Skip to content

Commit b3f870f

Browse files
authored
Make fewerBraces a standard feature (#16297)
Enables fewerBraces as a standard feature from 3.3 on. No language import is needed. The old import language.experimental.fewerBraces is deprecated but it still works. This is so that one can continue to mix fewerBraces in for projects compiling with earlier versions. We can turn the language import into a no-op or drop it completely when 3.3 is in the rear mirror.
2 parents 1e3739c + 43e3b7c commit b3f870f

23 files changed

+87
-116
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ object Feature:
101101
case Some(v) => v
102102
case none => sourceVersionSetting
103103

104-
def migrateTo3(using Context): Boolean = sourceVersion == `3.0-migration`
104+
def migrateTo3(using Context): Boolean =
105+
sourceVersion == `3.0-migration`
106+
107+
def fewerBracesEnabled(using Context) =
108+
sourceVersion.isAtLeast(`3.3`) || enabled(fewerBraces)
105109

106110
/** If current source migrates to `version`, issue given warning message
107111
* and return `true`, otherwise return `false`.

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ trait AllScalaSettings extends CommonScalaSettings, PluginSettings, VerboseSetti
6464
val oldSyntax: Setting[Boolean] = BooleanSetting("-old-syntax", "Require `(...)` around conditions.")
6565
val indent: Setting[Boolean] = BooleanSetting("-indent", "Together with -rewrite, remove {...} syntax when possible due to significant indentation.")
6666
val noindent: Setting[Boolean] = BooleanSetting("-no-indent", "Require classical {...} syntax, indentation is not significant.", aliases = List("-noindent"))
67-
val YindentColons: Setting[Boolean] = BooleanSetting("-Yindent-colons", "(disabled: use -language:experimental.fewerBraces instead)")
6867

6968
/* Decompiler settings */
7069
val printTasty: Setting[Boolean] = BooleanSetting("-print-tasty", "Prints the raw tasty.", aliases = List("--print-tasty"))

compiler/src/dotty/tools/dotc/config/SourceVersion.scala

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import util.Property
88
enum SourceVersion:
99
case `3.0-migration`, `3.0`, `3.1` // Note: do not add `3.1-migration` here, 3.1 is the same language as 3.0.
1010
case `3.2-migration`, `3.2`
11+
case `3.3-migration`, `3.3`
1112
case `future-migration`, `future`
1213

1314
val isMigrating: Boolean = toString.endsWith("-migration")

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+8-8
Original file line numberDiff line numberDiff line change
@@ -778,15 +778,14 @@ object Parsers {
778778
}
779779
})
780780
canRewrite &= (in.isAfterLineEnd || statCtdTokens.contains(in.token)) // test (5)
781-
if (canRewrite && (!underColonSyntax || in.fewerBracesEnabled)) {
781+
if canRewrite && (!underColonSyntax || Feature.fewerBracesEnabled) then
782782
val openingPatchStr =
783783
if !colonRequired then ""
784784
else if testChar(startOpening - 1, Chars.isOperatorPart(_)) then " :"
785785
else ":"
786786
val (startClosing, endClosing) = closingElimRegion()
787787
patch(source, Span(startOpening, endOpening), openingPatchStr)
788788
patch(source, Span(startClosing, endClosing), "")
789-
}
790789
t
791790
}
792791

@@ -1025,7 +1024,7 @@ object Parsers {
10251024
* body
10261025
*/
10271026
def isColonLambda =
1028-
in.fewerBracesEnabled && in.token == COLONfollow && followingIsLambdaAfterColon()
1027+
Feature.fewerBracesEnabled && in.token == COLONfollow && followingIsLambdaAfterColon()
10291028

10301029
/** operand { infixop operand | MatchClause } [postfixop],
10311030
*
@@ -2370,7 +2369,7 @@ object Parsers {
23702369
/** PostfixExpr ::= InfixExpr [id [nl]]
23712370
* InfixExpr ::= PrefixExpr
23722371
* | InfixExpr id [nl] InfixExpr
2373-
* | InfixExpr id `:` IndentedExpr
2372+
* | InfixExpr id ColonArgument
23742373
* | InfixExpr MatchClause
23752374
*/
23762375
def postfixExpr(location: Location = Location.ElseWhere): Tree =
@@ -2414,10 +2413,11 @@ object Parsers {
24142413
* | SimpleExpr `.` MatchClause
24152414
* | SimpleExpr (TypeArgs | NamedTypeArgs)
24162415
* | SimpleExpr1 ArgumentExprs
2417-
* | SimpleExpr1 `:` ColonArgument -- under language.experimental.fewerBraces
2418-
* ColonArgument ::= indent (CaseClauses | Block) outdent
2419-
* | FunParams (‘=>’ | ‘?=>’) ColonArgBody
2420-
* | HkTypeParamClause ‘=>’ ColonArgBody
2416+
* | SimpleExpr1 ColonArgument
2417+
* ColonArgument ::= colon [LambdaStart]
2418+
* indent (CaseClauses | Block) outdent
2419+
* LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
2420+
* | HkTypeParamClause ‘=>’
24212421
* ColonArgBody ::= indent (CaseClauses | Block) outdent
24222422
* Quoted ::= ‘'’ ‘{’ Block ‘}’
24232423
* | ‘'’ ‘[’ Type ‘]’

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

+1-20
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import scala.collection.mutable
1717
import scala.collection.immutable.SortedMap
1818
import rewrites.Rewrites.patch
1919
import config.Feature
20-
import config.Feature.migrateTo3
20+
import config.Feature.{migrateTo3, fewerBracesEnabled}
2121
import config.SourceVersion.`3.0`
2222
import reporting.{NoProfile, Profile}
2323

@@ -202,25 +202,6 @@ object Scanners {
202202
def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext)
203203
def erasedEnabled = featureEnabled(Feature.erasedDefinitions)
204204

205-
private inline val fewerBracesByDefault = false
206-
// turn on to study impact on codebase if `fewerBraces` was the default
207-
208-
private var fewerBracesEnabledCache = false
209-
private var fewerBracesEnabledCtx: Context = NoContext
210-
211-
def fewerBracesEnabled =
212-
if fewerBracesEnabledCtx ne myLanguageImportContext then
213-
fewerBracesEnabledCache =
214-
featureEnabled(Feature.fewerBraces)
215-
|| fewerBracesByDefault && indentSyntax && !migrateTo3
216-
// ensure that fewer braces is not the default for 3.0-migration since
217-
// { x: T =>
218-
// expr
219-
// }
220-
// would be ambiguous
221-
fewerBracesEnabledCtx = myLanguageImportContext
222-
fewerBracesEnabledCache
223-
224205
private var postfixOpsEnabledCache = false
225206
private var postfixOpsEnabledCtx: Context = NoContext
226207

docs/_docs/reference/other-new-features/indentation.md

+54-59
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,60 @@ Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>>
186186
Packaging ::= ‘package’ QualId :<<< TopStats >>>
187187
```
188188
189+
## Optional Braces for Method Arguments
190+
191+
Starting with Scala 3.3, a `<colon>` token is also recognized where a function argument would be expected. Examples:
192+
193+
```scala
194+
times(10):
195+
println("ah")
196+
println("ha")
197+
```
198+
199+
or
200+
201+
```scala
202+
credentials `++`:
203+
val file = Path.userHome / ".credentials"
204+
if file.exists
205+
then Seq(Credentials(file))
206+
else Seq()
207+
```
208+
209+
or
210+
211+
```scala
212+
xs.map:
213+
x =>
214+
val y = x - 1
215+
y * y
216+
```
217+
What's more, a `:` in these settings can also be followed on the same line by the parameter part and arrow of a lambda. So the last example could be compressed to this:
218+
219+
```scala
220+
xs.map: x =>
221+
val y = x - 1
222+
y * y
223+
```
224+
and the following would also be legal:
225+
```scala
226+
xs.foldLeft(0): (x, y) =>
227+
x + y
228+
```
229+
230+
The grammar changes for optional braces around arguments are as follows.
231+
232+
```
233+
SimpleExpr ::= ...
234+
| SimpleExpr ColonArgument
235+
InfixExpr ::= ...
236+
| InfixExpr id ColonArgument
237+
ColonArgument ::= colon [LambdaStart]
238+
indent (CaseClauses | Block) outdent
239+
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
240+
| HkTypeParamClause ‘=>’
241+
```
242+
189243
## Spaces vs Tabs
190244
191245
Indentation prefixes can consist of spaces and/or tabs. Indentation widths are the indentation prefixes themselves, ordered by the string prefix relation. So, so for instance "2 tabs, followed by 4 spaces" is strictly less than "2 tabs, followed by 5 spaces", but "2 tabs, followed by 4 spaces" is incomparable to "6 tabs" or to "4 spaces, followed by 2 tabs". It is an error if the indentation width of some line is incomparable with the indentation width of the region that's current at that point. To avoid such errors, it is a good idea not to mix spaces and tabs in the same source file.
@@ -448,62 +502,3 @@ indented regions where possible. When invoked with options `-rewrite -no-indent`
448502
The `-indent` option only works on [new-style syntax](./control-syntax.md). So to go from old-style syntax to new-style indented code one has to invoke the compiler twice, first with options `-rewrite -new-syntax`, then again with options
449503
`-rewrite -indent`. To go in the opposite direction, from indented code to old-style syntax, it's `-rewrite -no-indent`, followed by `-rewrite -old-syntax`.
450504

451-
## Variant: Indentation Marker `:` for Arguments
452-
453-
Generally, the possible indentation regions coincide with those regions where braces `{...}` are also legal, no matter whether the braces enclose an expression or a set of definitions. There is one exception, though: Arguments to functions can be enclosed in braces but they cannot be simply indented instead. Making indentation always significant for function arguments would be too restrictive and fragile.
454-
455-
To allow such arguments to be written without braces, a variant of the indentation scheme is implemented under language import
456-
```scala
457-
import language.experimental.fewerBraces
458-
```
459-
In this variant, a `<colon>` token is also recognized where function argument would be expected. Examples:
460-
461-
```scala
462-
times(10):
463-
println("ah")
464-
println("ha")
465-
```
466-
467-
or
468-
469-
```scala
470-
credentials `++`:
471-
val file = Path.userHome / ".credentials"
472-
if file.exists
473-
then Seq(Credentials(file))
474-
else Seq()
475-
```
476-
477-
or
478-
479-
```scala
480-
xs.map:
481-
x =>
482-
val y = x - 1
483-
y * y
484-
```
485-
What's more, a `:` in these settings can also be followed on the same line by the parameter part and arrow of a lambda. So the last example could be compressed to this:
486-
487-
```scala
488-
xs.map: x =>
489-
val y = x - 1
490-
y * y
491-
```
492-
and the following would also be legal:
493-
```scala
494-
xs.foldLeft(0): (x, y) =>
495-
x + y
496-
```
497-
498-
The grammar changes for this variant are as follows.
499-
500-
```
501-
SimpleExpr ::= ...
502-
| SimpleExpr ColonArgument
503-
InfixExpr ::= ...
504-
| InfixExpr id ColonArgument
505-
ColonArgument ::= colon [LambdaStart]
506-
indent (CaseClauses | Block) outdent
507-
LambdaStart ::= FunParams (‘=>|?=>’)
508-
| HkTypeParamClause=>
509-
```

docs/_docs/reference/syntax.md

+6
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ Catches ::= ‘catch’ (Expr | ExprCaseClause)
249249
PostfixExpr ::= InfixExpr [id] -- only if language.postfixOperators is enabled
250250
InfixExpr ::= PrefixExpr
251251
| InfixExpr id [nl] InfixExpr
252+
| InfixExpr id ColonArgument
252253
| InfixExpr MatchClause
253254
MatchClause ::= ‘match’ <<< CaseClauses >>>
254255
PrefixExpr ::= [PrefixOperator] SimpleExpr
@@ -267,6 +268,11 @@ SimpleExpr ::= SimpleRef
267268
| SimpleExpr ‘.’ MatchClause
268269
| SimpleExpr TypeArgs
269270
| SimpleExpr ArgumentExprs
271+
| SimpleExpr ColonArgument
272+
ColonArgument ::= colon [LambdaStart]
273+
indent (CaseClauses | Block) outdent
274+
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
275+
| HkTypeParamClause ‘=>’
270276
Quoted ::= ‘'’ ‘{’ Block ‘}’
271277
| ‘'’ ‘[’ Type ‘]’
272278
ExprSplice ::= spliceId -- if inside quoted block

library/src/scala/runtime/stdLibPatches/language.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ object language:
5151
/** Experimental support for using indentation for arguments
5252
*/
5353
@compileTimeOnly("`fewerBraces` can only be used at compile time in import statements")
54+
@deprecated("`fewerBraces` is now standard, no language import is needed", since = "3.3")
5455
object fewerBraces
5556

5657
/** Experimental support for typechecked exception capabilities
@@ -192,7 +193,6 @@ object language:
192193
@compileTimeOnly("`3.2` can only be used at compile time in import statements")
193194
object `3.2`
194195

195-
/* This can be added when we go to 3.3
196196
/** Set source version to 3.3-migration.
197197
*
198198
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
@@ -206,5 +206,5 @@ object language:
206206
*/
207207
@compileTimeOnly("`3.3` can only be used at compile time in import statements")
208208
object `3.3`
209-
*/
209+
210210
end language

tests/neg-custom-args/no-experimental/experimental-nested-imports-2.scala

-4
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
import annotation.experimental
22

33
class Class1:
4-
import language.experimental.fewerBraces // error
54
import language.experimental.namedTypeArguments // error
65
import language.experimental.genericNumberLiterals // error
76
import language.experimental.erasedDefinitions // ok: only check at erased definition
87
@experimental def f = 1
98
def g = 1
109

1110
object Object1:
12-
import language.experimental.fewerBraces // error
1311
import language.experimental.namedTypeArguments // error
1412
import language.experimental.genericNumberLiterals // error
1513
import language.experimental.erasedDefinitions // ok: only check at erased definition
1614
@experimental def f = 1
1715
def g = 1
1816

1917
def fun1 =
20-
import language.experimental.fewerBraces // error
2118
import language.experimental.namedTypeArguments // error
2219
import language.experimental.genericNumberLiterals // error
2320
import language.experimental.erasedDefinitions // ok: only check at erased definition
2421
@experimental def f = 1
2522
def g = 1
2623

2724
val value1 =
28-
import language.experimental.fewerBraces // error
2925
import language.experimental.namedTypeArguments // error
3026
import language.experimental.genericNumberLiterals // error
3127
import language.experimental.erasedDefinitions // ok: only check at erased definition
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
import annotation.experimental
22

33
class Class1:
4-
import language.experimental.fewerBraces // error
54
import language.experimental.namedTypeArguments // error
65
import language.experimental.genericNumberLiterals // error
76
import language.experimental.erasedDefinitions // ok: only check at erased definition
87

98
object Object1:
10-
import language.experimental.fewerBraces // error
119
import language.experimental.namedTypeArguments // error
1210
import language.experimental.genericNumberLiterals // error
1311
import language.experimental.erasedDefinitions // ok: only check at erased definition
1412

1513
def fun1 =
16-
import language.experimental.fewerBraces // error
1714
import language.experimental.namedTypeArguments // error
1815
import language.experimental.genericNumberLiterals // error
1916
import language.experimental.erasedDefinitions // ok: only check at erased definition
2017

2118
val value1 =
22-
import language.experimental.fewerBraces // error
2319
import language.experimental.namedTypeArguments // error
2420
import language.experimental.genericNumberLiterals // error
2521
import language.experimental.erasedDefinitions // ok: only check at erased definition

tests/neg-custom-args/no-experimental/experimental-nested-imports.scala

-4
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
import annotation.experimental
22

33
class Class1:
4-
import language.experimental.fewerBraces // error
54
import language.experimental.namedTypeArguments // error
65
import language.experimental.genericNumberLiterals // error
76
import language.experimental.erasedDefinitions // ok: only check at erased definition
87
@experimental def f = 1
98

109
object Object1:
11-
import language.experimental.fewerBraces // error
1210
import language.experimental.namedTypeArguments // error
1311
import language.experimental.genericNumberLiterals // error
1412
import language.experimental.erasedDefinitions // ok: only check at erased definition
1513
@experimental def f = 1
1614

1715
def fun1 =
18-
import language.experimental.fewerBraces // error
1916
import language.experimental.namedTypeArguments // error
2017
import language.experimental.genericNumberLiterals // error
2118
import language.experimental.erasedDefinitions // ok: only check at erased definition
2219
@experimental def f = 1
2320

2421
val value1 =
25-
import language.experimental.fewerBraces // error
2622
import language.experimental.namedTypeArguments // error
2723
import language.experimental.genericNumberLiterals // error
2824
import language.experimental.erasedDefinitions // ok: only check at erased definition

tests/neg-custom-args/no-experimental/experimental-package-imports.scala

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import annotation.experimental
22

33
package foo {
4-
import language.experimental.fewerBraces // error
54
import language.experimental.namedTypeArguments // error
65
import language.experimental.genericNumberLiterals // error
76
import language.experimental.erasedDefinitions // ok: only check at erased definition
@@ -13,7 +12,6 @@ package foo {
1312

1413
package foo2 {
1514
// ok: all definitions are top-level @experimental
16-
import language.experimental.fewerBraces
1715
import language.experimental.namedTypeArguments
1816
import language.experimental.genericNumberLiterals
1917
import language.experimental.erasedDefinitions

tests/neg/closure-args.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import language.experimental.fewerBraces
1+
import language.`3.3`
22

33
val x = List(1).map: (x: => Int) => // error
44
???

tests/neg/i10943.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import language.experimental.fewerBraces
1+
import language.`3.3`
22

33
object T:
44
class A

0 commit comments

Comments
 (0)