Skip to content

Commit 1028f8a

Browse files
committed
Change fewerBraces to always require colon
1 parent 5851c62 commit 1028f8a

File tree

13 files changed

+186
-108
lines changed

13 files changed

+186
-108
lines changed

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

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ object Parsers {
4343
enum Location(val inParens: Boolean, val inPattern: Boolean, val inArgs: Boolean):
4444
case InParens extends Location(true, false, false)
4545
case InArgs extends Location(true, false, true)
46+
case InColonArg extends Location(false, false, true)
4647
case InPattern extends Location(false, true, false)
4748
case InGuard extends Location(false, false, false)
4849
case InPatternArgs extends Location(false, true, true) // InParens not true, since it might be an alternative
@@ -431,7 +432,7 @@ object Parsers {
431432
convertToParam(t, mods) :: Nil
432433
case Tuple(ts) =>
433434
ts.map(convertToParam(_, mods))
434-
case t: Typed =>
435+
case t @ Typed(Ident(_), _) =>
435436
report.errorOrMigrationWarning(
436437
em"parentheses are required around the parameter of a lambda${rewriteNotice()}",
437438
in.sourcePos(), from = `3.0`)
@@ -444,20 +445,24 @@ object Parsers {
444445

445446
/** Convert tree to formal parameter
446447
*/
447-
def convertToParam(tree: Tree, mods: Modifiers, expected: String = "formal parameter"): ValDef = tree match {
448-
case id @ Ident(name) =>
449-
makeParameter(name.asTermName, TypeTree(), mods, isBackquoted = isBackquoted(id)).withSpan(tree.span)
450-
case Typed(_, tpt: TypeBoundsTree) =>
451-
syntaxError(s"not a legal $expected", tree.span)
452-
makeParameter(nme.ERROR, tree, mods)
453-
case Typed(id @ Ident(name), tpt) =>
454-
makeParameter(name.asTermName, tpt, mods, isBackquoted = isBackquoted(id)).withSpan(tree.span)
455-
case Typed(Splice(Ident(name)), tpt) =>
456-
makeParameter(("$" + name).toTermName, tpt, mods).withSpan(tree.span)
457-
case _ =>
458-
syntaxError(s"not a legal $expected", tree.span)
448+
def convertToParam(tree: Tree, mods: Modifiers): ValDef =
449+
def fail() =
450+
syntaxError(s"not a legal formal parameter for a function literal", tree.span)
459451
makeParameter(nme.ERROR, tree, mods)
460-
}
452+
tree match
453+
case param: ValDef =>
454+
param.withMods(param.mods | mods.flags)
455+
case id @ Ident(name) =>
456+
makeParameter(name.asTermName, TypeTree(), mods, isBackquoted = isBackquoted(id)).withSpan(tree.span)
457+
// the following three cases are needed only for 2.x parameters without enclosing parentheses
458+
case Typed(_, tpt: TypeBoundsTree) =>
459+
fail()
460+
case Typed(id @ Ident(name), tpt) =>
461+
makeParameter(name.asTermName, tpt, mods, isBackquoted = isBackquoted(id)).withSpan(tree.span)
462+
case Typed(Splice(Ident(name)), tpt) =>
463+
makeParameter(("$" + name).toTermName, tpt, mods).withSpan(tree.span)
464+
case _ =>
465+
fail()
461466

462467
/** Convert (qual)ident to type identifier
463468
*/
@@ -891,9 +896,8 @@ object Parsers {
891896
val next = in.lookahead.token
892897
next == LBRACKET || next == LPAREN
893898

894-
895899
def followingIsSelfType() =
896-
val lookahead = in.LookaheadScanner()
900+
val lookahead = in.LookaheadScanner(allowIndent = true)
897901
lookahead.nextToken()
898902
lookahead.token == COLON
899903
&& {
@@ -915,6 +919,43 @@ object Parsers {
915919
}
916920
}
917921

922+
/** When encountering a `:`, is that in the binding of a lambda?
923+
* @pre location of the enclosing expression is `InParens`, so there is an open `(`.
924+
*/
925+
def followingIsLambdaParams() =
926+
val lookahead = in.LookaheadScanner()
927+
lookahead.nextToken()
928+
while lookahead.token != RPAREN && lookahead.token != EOF do
929+
if lookahead.token == LPAREN then lookahead.skipParens()
930+
else lookahead.nextToken()
931+
lookahead.token == RPAREN
932+
&& {
933+
lookahead.nextToken()
934+
lookahead.isArrow
935+
}
936+
937+
/** Is the token sequence following the current `:` token classified as a lambda?
938+
* This is the case if the input starts with an identifier, a wildcard, or
939+
* something enclosed in (...) or [...], and this is followed by a `=>` or `?=>`
940+
* and an INDENT.
941+
*/
942+
def followingIsLambdaAfterColon(): Boolean =
943+
val lookahead = in.LookaheadScanner(allowIndent = true)
944+
def isArrowIndent() =
945+
lookahead.isArrow
946+
&& {
947+
lookahead.nextToken()
948+
lookahead.token == INDENT
949+
}
950+
lookahead.nextToken()
951+
if lookahead.isIdent || lookahead.token == USCORE then
952+
lookahead.nextToken()
953+
isArrowIndent()
954+
else if lookahead.token == LPAREN || lookahead.token == LBRACKET then
955+
lookahead.skipParens()
956+
isArrowIndent()
957+
else false
958+
918959
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
919960

920961
var opStack: List[OpInfo] = Nil
@@ -2209,7 +2250,11 @@ object Parsers {
22092250
in.nextToken()
22102251
else
22112252
accept(ARROW)
2212-
Function(params, if (location == Location.InBlock) block() else expr())
2253+
val body =
2254+
if location == Location.InBlock then block()
2255+
else if location == Location.InColonArg && in.token == INDENT then blockExpr()
2256+
else expr()
2257+
Function(params, body)
22132258
}
22142259

22152260
/** PostfixExpr ::= InfixExpr [id [nl]]
@@ -2259,9 +2304,11 @@ object Parsers {
22592304
* | SimpleExpr `.` MatchClause
22602305
* | SimpleExpr (TypeArgs | NamedTypeArgs)
22612306
* | SimpleExpr1 ArgumentExprs
2262-
* | SimpleExpr1 `:` IndentedExpr -- under language.experimental.fewerBraces
2263-
* | SimpleExpr1 FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces
2264-
* IndentedExpr ::= indent (CaseClauses | Block) outdent
2307+
* | SimpleExpr1 `:` ColonArgument -- under language.experimental.fewerBraces
2308+
* ColonArgument ::= indent (CaseClauses | Block) outdent
2309+
* | FunParams (‘=>’ | ‘?=>’) ColonArgBody
2310+
* | HkTypeParamClause ‘=>’ ColonArgBody
2311+
* ColonArgBody ::= indent (CaseClauses | Block) outdent
22652312
* Quoted ::= ‘'’ ‘{’ Block ‘}’
22662313
* | ‘'’ ‘[’ Type ‘]’
22672314
*/
@@ -2283,7 +2330,7 @@ object Parsers {
22832330
placeholderParams = param :: placeholderParams
22842331
atSpan(start) { Ident(pname) }
22852332
case LPAREN =>
2286-
atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOpt())) }
2333+
atSpan(in.offset) { makeTupleOrParens(inParens(exprsInParensOrBindings())) }
22872334
case LBRACE | INDENT =>
22882335
canApply = false
22892336
blockExpr()
@@ -2317,55 +2364,38 @@ object Parsers {
23172364
simpleExprRest(t, location, canApply)
23182365
}
23192366

2320-
def simpleExprRest(t: Tree, location: Location, canApply: Boolean = true): Tree = {
2367+
def simpleExprRest(t: Tree, location: Location, canApply: Boolean = true): Tree =
23212368
if (canApply) argumentStart()
2322-
in.token match {
2369+
in.token match
23232370
case DOT =>
23242371
in.nextToken()
23252372
simpleExprRest(selectorOrMatch(t), location, canApply = true)
23262373
case LBRACKET =>
23272374
val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) }
23282375
simpleExprRest(tapp, location, canApply = true)
2329-
case LPAREN if canApply =>
2330-
val app = atSpan(startOffset(t), in.offset) {
2331-
val argExprs @ (args, isUsing) = argumentExprs()
2332-
if !isUsing && in.isArrow && location != Location.InGuard && in.fewerBracesEnabled then
2333-
val params = convertToParams(Tuple(args))
2334-
if params.forall(_.name != nme.ERROR) then
2335-
applyToClosure(t, in.offset, params)
2336-
else
2337-
mkApply(t, argExprs)
2338-
else
2339-
mkApply(t, argExprs)
2340-
}
2341-
simpleExprRest(app, location, canApply = true)
2342-
case LBRACE | INDENT if canApply =>
2376+
case LPAREN | LBRACE | INDENT if canApply =>
23432377
val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) }
23442378
simpleExprRest(app, location, canApply = true)
23452379
case USCORE =>
2346-
if in.lookahead.isArrow && location != Location.InGuard && in.fewerBracesEnabled then
2347-
val app = applyToClosure(t, in.offset, convertToParams(wildcardIdent()))
2348-
simpleExprRest(app, location, canApply = true)
2349-
else
2350-
atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) }
2351-
case IDENTIFIER
2352-
if !in.isOperator && in.lookahead.isArrow && location != Location.InGuard && in.fewerBracesEnabled =>
2353-
val app = applyToClosure(t, in.offset, convertToParams(termIdent()))
2354-
simpleExprRest(app, location, canApply = true)
2380+
atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) }
23552381
case _ =>
2356-
t
2357-
}
2358-
}
2359-
2360-
def applyToClosure(t: Tree, start: Offset, params: List[ValDef]): Tree =
2361-
atSpan(startOffset(t), in.offset) {
2362-
val arg = atSpan(start, in.skipToken()) {
2363-
if in.token != INDENT then
2364-
syntaxErrorOrIncomplete(i"indented expression expected, ${in} found")
2365-
Function(params, blockExpr())
2366-
}
2367-
Apply(t, arg)
2368-
}
2382+
if in.isColon() && location == Location.InParens && followingIsLambdaParams() then
2383+
t match
2384+
case id @ Ident(name) =>
2385+
if name.is(WildcardParamName) then
2386+
assert(name == placeholderParams.head.name)
2387+
placeholderParams = placeholderParams.tail
2388+
atSpan(startOffset(id)) {
2389+
makeParameter(name.asTermName, typedOpt(), Modifiers(), isBackquoted = isBackquoted(id))
2390+
}
2391+
case _ => t
2392+
else if in.fewerBracesEnabled && in.token == COLON && followingIsLambdaAfterColon() then
2393+
val app = atSpan(startOffset(t), in.skipToken()) {
2394+
Apply(t, expr(Location.InColonArg) :: Nil)
2395+
}
2396+
simpleExprRest(app, location, canApply = true)
2397+
else t
2398+
end simpleExprRest
23692399

23702400
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]
23712401
* | ‘new’ TemplateBody
@@ -2387,9 +2417,20 @@ object Parsers {
23872417
}
23882418

23892419
/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
2420+
* Bindings ::= Binding {`,' Binding}
23902421
*/
2391-
def exprsInParensOpt(): List[Tree] =
2392-
if (in.token == RPAREN) Nil else commaSeparated(exprInParens)
2422+
def exprsInParensOrBindings(): List[Tree] =
2423+
if in.token == RPAREN then Nil
2424+
else in.currentRegion.withCommasExpected {
2425+
var isFormalParams = false
2426+
def exprOrBinding() =
2427+
if isFormalParams then binding(Modifiers())
2428+
else
2429+
val t = exprInParens()
2430+
if t.isInstanceOf[ValDef] then isFormalParams = true
2431+
t
2432+
commaSeparatedRest(exprOrBinding(), exprOrBinding)
2433+
}
23932434

23942435
/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
23952436
* | `(' [ExprsInParens `,'] PostfixExpr `*' ')'

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,10 @@ object Scanners {
189189
val indentSyntax =
190190
((if (Config.defaultIndent) !noindentSyntax else ctx.settings.indent.value)
191191
|| rewriteNoIndent)
192-
&& !isInstanceOf[LookaheadScanner]
192+
&& { this match
193+
case self: LookaheadScanner => self.allowIndent
194+
case _ => true
195+
}
193196

194197
if (rewrite) {
195198
val s = ctx.settings
@@ -206,12 +209,17 @@ object Scanners {
206209
def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext)
207210
def erasedEnabled = featureEnabled(Feature.erasedDefinitions)
208211

212+
private inline val fewerBracesByDefault = false
213+
// turn on to study impact on codebase if `fewerBraces` was the default
214+
209215
private var fewerBracesEnabledCache = false
210216
private var fewerBracesEnabledCtx: Context = NoContext
211217

212218
def fewerBracesEnabled =
213219
if fewerBracesEnabledCtx ne myLanguageImportContext then
214-
fewerBracesEnabledCache = featureEnabled(Feature.fewerBraces)
220+
fewerBracesEnabledCache =
221+
featureEnabled(Feature.fewerBraces)
222+
|| fewerBracesByDefault && indentSyntax
215223
fewerBracesEnabledCtx = myLanguageImportContext
216224
fewerBracesEnabledCache
217225

@@ -1067,7 +1075,7 @@ object Scanners {
10671075
reset()
10681076
next
10691077

1070-
class LookaheadScanner() extends Scanner(source, offset) {
1078+
class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset) {
10711079
override def languageImportContext = Scanner.this.languageImportContext
10721080
}
10731081

docs/_docs/internals/syntax.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,13 @@ SimpleExpr ::= SimpleRef
254254
| SimpleExpr ‘.’ MatchClause
255255
| SimpleExpr TypeArgs TypeApply(expr, args)
256256
| SimpleExpr ArgumentExprs Apply(expr, args)
257-
| SimpleExpr ‘:’ IndentedExpr -- under language.experimental.fewerBraces
258-
| SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces
257+
| SimpleExpr ‘:’ ColonArgument -- under language.experimental.fewerBraces
259258
| SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped)
260-
| XmlExpr -- to be dropped
261-
IndentedExpr ::= indent CaseClauses | Block outdent
262-
Quoted ::= ‘'’ ‘{’ Block ‘}’
263-
| ‘'’ ‘[’ Type ‘]’
259+
| XmlExpr -- to be dropped
260+
ColonArgument ::= indent CaseClauses | Block outdent
261+
| FunParams (‘=>’ | ‘?=>’) ColonArgBody
262+
| HkTypeParamClause ‘=>’ ColonArgBody
263+
ColonArgBody ::= indent (CaseClauses | Block) outdent
264264
ExprsInParens ::= ExprInParens {‘,’ ExprInParens}
265265
ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here
266266
| Expr

tests/neg-custom-args/nowarn/nowarn.check

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Matching filters for @nowarn or -Wconf:
6363
| ^
6464
| method f is deprecated
6565
-- Deprecation Warning: tests/neg-custom-args/nowarn/nowarn.scala:47:10 ------------------------------------------------
66-
47 |def t7c = f: // warning (deprecation)
66+
47 |def t7c = f // warning (deprecation)
6767
| ^
6868
| method f is deprecated
6969
-- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:53:7 ---------------------------------------------------
@@ -78,10 +78,10 @@ Matching filters for @nowarn or -Wconf:
7878
40 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation)
7979
|^^^^^^^^^^^^^^^^^^^
8080
|@nowarn annotation does not suppress any warnings
81-
-- Error: tests/neg-custom-args/nowarn/nowarn.scala:48:3 ---------------------------------------------------------------
82-
48 | @nowarn("msg=fish") // error (unused nowarn)
83-
| ^^^^^^^^^^^^^^^^^^^
84-
| @nowarn annotation does not suppress any warnings
81+
-- Error: tests/neg-custom-args/nowarn/nowarn.scala:48:5 ---------------------------------------------------------------
82+
48 | : @nowarn("msg=fish") // error (unused nowarn)
83+
| ^^^^^^^^^^^^^^^^^^^
84+
| @nowarn annotation does not suppress any warnings
8585
-- Error: tests/neg-custom-args/nowarn/nowarn.scala:60:0 ---------------------------------------------------------------
8686
60 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused)
8787
|^^^^^^^

tests/neg-custom-args/nowarn/nowarn.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ def t6a = f // warning (refchecks, deprecation)
4242
@nowarn def t6f = f
4343

4444
def t7a = f: @nowarn("cat=deprecation")
45-
def t7b = f:
46-
@nowarn("msg=deprecated")
47-
def t7c = f: // warning (deprecation)
48-
@nowarn("msg=fish") // error (unused nowarn)
45+
def t7b = f
46+
: @nowarn("msg=deprecated")
47+
def t7c = f // warning (deprecation)
48+
: @nowarn("msg=fish") // error (unused nowarn)
4949
def t7d = f: @nowarn("")
5050
def t7e = f: @nowarn
5151

tests/neg/closure-args.scala

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
import language.experimental.fewerBraces
22

3-
val x = List().map (x: => Int) => // error
3+
val x = List().map: (x: => Int) => // error
44
???
5-
val y = List() map x => // error
5+
val y = List() map: x => // error
66
x + 1 // error
7-
val z = List() map + => // error
7+
val z = List().map: + => // ok
88
???
99

10+
val xs = List(1)
11+
val b: Int = xs // error
12+
.map: x => x * x // error
13+
.filter: y => y > 0 // error
14+
(0)
15+
val d = xs // error
16+
.map: x => x.toString + xs.dropWhile:
17+
y => y > 0
18+
19+
val c = List(xs.map: y => y + y) // error // error
20+
val d2: String = xs // error
21+
.map: x => x.toString + xs.dropWhile: y => y > 0 // error // error
22+
.filter: z => !z.isEmpty // error
23+
(0)
24+
25+
val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // error

tests/neg/i13769.check

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
-- Error: tests/neg/i13769.scala:2:18 ----------------------------------------------------------------------------------
2-
2 |val te = tup.map((x: _ <: Int) => List(x)) // error // error
3-
| ^^^^^^^^^^^
4-
| not a legal formal parameter
5-
-- [E006] Not Found Error: tests/neg/i13769.scala:2:39 -----------------------------------------------------------------
6-
2 |val te = tup.map((x: _ <: Int) => List(x)) // error // error
7-
| ^
8-
| Not found: x
1+
-- [E035] Syntax Error: tests/neg/i13769.scala:2:21 --------------------------------------------------------------------
2+
2 |val te = tup.map((x: _ <: Int) => List(x)) // error
3+
| ^^^^^^^^
4+
| Unbound wildcard type
95
|
106
| longer explanation available when compiling with `-explain`

tests/neg/i13769.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
val tup = (1, "s")
2-
val te = tup.map((x: _ <: Int) => List(x)) // error // error
2+
val te = tup.map((x: _ <: Int) => List(x)) // error

tests/neg/i1424.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
class Test {
2-
(x: Int) => x // error: not a legal self type clause // error: not found x
2+
(x: Int) => x // error: not a legal self type clause
33
}

0 commit comments

Comments
 (0)