Skip to content

Commit 9f61741

Browse files
committed
Change fewer-braces parsing to new scheme
- Require a colon in front of a lambda argument - Allow lambda arguments on one line
1 parent 40cc861 commit 9f61741

File tree

6 files changed

+133
-92
lines changed

6 files changed

+133
-92
lines changed

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

Lines changed: 62 additions & 60 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
@@ -893,7 +894,6 @@ object Parsers {
893894
val next = in.lookahead.token
894895
next == LBRACKET || next == LPAREN
895896

896-
897897
def followingIsSelfType() =
898898
val lookahead = in.LookaheadScanner()
899899
lookahead.nextToken()
@@ -917,10 +917,10 @@ object Parsers {
917917
}
918918
}
919919

920-
/** When encountering a `:`, is that in the first binding of a lambda?
921-
* @pre location of the enclosing expression is `InParens`, so there is am open `(`.
920+
/** When encountering a `:`, is that in the binding of a lambda?
921+
* @pre location of the enclosing expression is `InParens`, so there is an open `(`.
922922
*/
923-
def followingisLambdaParams() =
923+
def followingIsLambdaParams() =
924924
val lookahead = in.LookaheadScanner()
925925
lookahead.nextToken()
926926
while lookahead.token != RPAREN && lookahead.token != EOF do
@@ -932,6 +932,21 @@ object Parsers {
932932
lookahead.isArrow
933933
}
934934

935+
/** Is the token sequence following the current `:` token classified as a lambda?
936+
* This is the case if the input starts with an identifier, a wildcard, or
937+
* something enclosed in (...) or [...], and this is followed by a `=>` or `?=>`.
938+
*/
939+
def followingIsLambdaAfterColon(): Boolean =
940+
val lookahead = in.LookaheadScanner()
941+
lookahead.nextToken()
942+
if lookahead.isIdent || lookahead.token == USCORE then
943+
lookahead.nextToken()
944+
lookahead.isArrow
945+
else if lookahead.token == LPAREN || lookahead.token == LBRACKET then
946+
lookahead.skipParens()
947+
lookahead.isArrow
948+
else false
949+
935950
/* --------- OPERAND/OPERATOR STACK --------------------------------------- */
936951

937952
var opStack: List[OpInfo] = Nil
@@ -2229,7 +2244,17 @@ object Parsers {
22292244
in.nextToken()
22302245
else
22312246
accept(ARROW)
2232-
Function(params, if (location == Location.InBlock) block() else expr())
2247+
val body =
2248+
if location == Location.InBlock then block()
2249+
else if location == Location.InColonArg then
2250+
if in.token == INDENT then
2251+
blockExpr()
2252+
else
2253+
in.insertMaxIndent()
2254+
try casesOrBlock(simplify = true)
2255+
finally accept(OUTDENT)
2256+
else expr()
2257+
Function(params, body)
22332258
}
22342259

22352260
/** PostfixExpr ::= InfixExpr [id [nl]]
@@ -2279,9 +2304,12 @@ object Parsers {
22792304
* | SimpleExpr `.` MatchClause
22802305
* | SimpleExpr (TypeArgs | NamedTypeArgs)
22812306
* | SimpleExpr1 ArgumentExprs
2282-
* | SimpleExpr1 `:` IndentedExpr -- under language.experimental.fewerBraces
2283-
* | SimpleExpr1 FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces
2284-
* 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
2312+
* | <silent-indent> (CaseClauses | Block) outdent -- silent-indent is inserted by Scanner if no real indent is found
22852313
* Quoted ::= ‘'’ ‘{’ Block ‘}’
22862314
* | ‘'’ ‘[’ Type ‘]’
22872315
*/
@@ -2337,65 +2365,38 @@ object Parsers {
23372365
simpleExprRest(t, location, canApply)
23382366
}
23392367

2340-
def simpleExprRest(t: Tree, location: Location, canApply: Boolean = true): Tree = {
2368+
def simpleExprRest(t: Tree, location: Location, canApply: Boolean = true): Tree =
23412369
if (canApply) argumentStart()
2342-
in.token match {
2370+
in.token match
23432371
case DOT =>
23442372
in.nextToken()
23452373
simpleExprRest(selectorOrMatch(t), location, canApply = true)
23462374
case LBRACKET =>
23472375
val tapp = atSpan(startOffset(t), in.offset) { TypeApply(t, typeArgs(namedOK = true, wildOK = false)) }
23482376
simpleExprRest(tapp, location, canApply = true)
2349-
case LPAREN if canApply =>
2350-
val app = atSpan(startOffset(t), in.offset) {
2351-
val argExprs @ (args, isUsing) = argumentExprs()
2352-
if !isUsing && in.isArrow && location != Location.InGuard && in.fewerBracesEnabled then
2353-
val params = convertToParams(Tuple(args))
2354-
if params.forall(_.name != nme.ERROR) then
2355-
applyToClosure(t, in.offset, params)
2356-
else
2357-
mkApply(t, argExprs)
2358-
else
2359-
mkApply(t, argExprs)
2360-
}
2361-
simpleExprRest(app, location, canApply = true)
2362-
case LBRACE | INDENT if canApply =>
2377+
case LPAREN | LBRACE | INDENT if canApply =>
23632378
val app = atSpan(startOffset(t), in.offset) { mkApply(t, argumentExprs()) }
23642379
simpleExprRest(app, location, canApply = true)
23652380
case USCORE =>
2366-
if in.lookahead.isArrow && location != Location.InGuard && in.fewerBracesEnabled then
2367-
val app = applyToClosure(t, in.offset, convertToParams(wildcardIdent()))
2368-
simpleExprRest(app, location, canApply = true)
2369-
else
2370-
atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) }
2371-
case IDENTIFIER
2372-
if !in.isOperator && in.lookahead.isArrow && location != Location.InGuard && in.fewerBracesEnabled =>
2373-
val app = applyToClosure(t, in.offset, convertToParams(termIdent()))
2374-
simpleExprRest(app, location, canApply = true)
2381+
atSpan(startOffset(t), in.skipToken()) { PostfixOp(t, Ident(nme.WILDCARD)) }
23752382
case _ =>
2376-
t match
2377-
case id @ Ident(name)
2378-
if in.isColon() && location == Location.InParens && followingisLambdaParams() =>
2379-
if name.is(WildcardParamName) then
2380-
assert(name == placeholderParams.head.name)
2381-
placeholderParams = placeholderParams.tail
2382-
atSpan(startOffset(id)) {
2383-
makeParameter(name.asTermName, typedOpt(), Modifiers(), isBackquoted = isBackquoted(id))
2384-
}
2385-
case _ =>
2386-
t
2387-
}
2388-
}
2389-
2390-
def applyToClosure(t: Tree, start: Offset, params: List[ValDef]): Tree =
2391-
atSpan(startOffset(t), in.offset) {
2392-
val arg = atSpan(start, in.skipToken()) {
2393-
if in.token != INDENT then
2394-
syntaxErrorOrIncomplete(i"indented expression expected, ${in} found")
2395-
Function(params, blockExpr())
2396-
}
2397-
Apply(t, arg)
2398-
}
2383+
if in.isColon() && location == Location.InParens && followingIsLambdaParams() then
2384+
t match
2385+
case id @ Ident(name) =>
2386+
if name.is(WildcardParamName) then
2387+
assert(name == placeholderParams.head.name)
2388+
placeholderParams = placeholderParams.tail
2389+
atSpan(startOffset(id)) {
2390+
makeParameter(name.asTermName, typedOpt(), Modifiers(), isBackquoted = isBackquoted(id))
2391+
}
2392+
case _ => t
2393+
else if in.fewerBracesEnabled && in.token == COLON && followingIsLambdaAfterColon() then
2394+
val app = atSpan(startOffset(t), in.skipToken()) {
2395+
Apply(t, expr(Location.InColonArg) :: Nil)
2396+
}
2397+
simpleExprRest(app, location, canApply = true)
2398+
else t
2399+
end simpleExprRest
23992400

24002401
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]
24012402
* | ‘new’ TemplateBody
@@ -2499,12 +2500,13 @@ object Parsers {
24992500
*/
25002501
def blockExpr(): Tree = atSpan(in.offset) {
25012502
val simplify = in.token == INDENT
2502-
inDefScopeBraces {
2503-
if (in.token == CASE) Match(EmptyTree, caseClauses(() => caseClause()))
2504-
else block(simplify)
2505-
}
2503+
inDefScopeBraces { casesOrBlock(simplify) }
25062504
}
25072505

2506+
def casesOrBlock(simplify: Boolean): Tree =
2507+
if (in.token == CASE) Match(EmptyTree, caseClauses(() => caseClause()))
2508+
else block(simplify)
2509+
25082510
/** Block ::= BlockStatSeq
25092511
* @note Return tree does not have a defined span.
25102512
*/

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -610,9 +610,13 @@ object Scanners {
610610
if next.token != COLON then
611611
handleNewIndentWidth(r.enclosing, ir =>
612612
errorButContinue(
613-
i"""The start of this line does not match any of the previous indentation widths.
614-
|Indentation width of current line : $nextWidth
615-
|This falls between previous widths: ${ir.width} and $lastWidth"""))
613+
if r.indentWidth == IndentWidth.Max then
614+
i"""Enclosing expression is nested in a one line lambda expression following `:`.
615+
|It may not spill over to a new line."""
616+
else
617+
i"""The start of this line does not match any of the previous indentation widths.
618+
|Indentation width of current line : $nextWidth
619+
|This falls between previous widths: ${ir.width} and $lastWidth"""))
616620
case r =>
617621
if skipping then
618622
if r.enclosing.isClosedByUndentAt(nextWidth) then
@@ -653,7 +657,9 @@ object Scanners {
653657
currentRegion = Indented(nextWidth, COLONEOL, currentRegion)
654658
offset = next.offset
655659
token = INDENT
656-
end observeIndented
660+
661+
def insertMaxIndent() =
662+
currentRegion = Indented(IndentWidth.Max, ARROW, currentRegion)
657663

658664
/** Insert an <outdent> token if next token closes an indentation region.
659665
* Exception: continue if indentation region belongs to a `match` and next token is `case`.
@@ -1643,25 +1649,29 @@ object Scanners {
16431649
enum IndentWidth {
16441650
case Run(ch: Char, n: Int)
16451651
case Conc(l: IndentWidth, r: Run)
1652+
case Max // delimits one-line lambdas following a `:`
16461653

1647-
def <= (that: IndentWidth): Boolean = this match {
1654+
def <= (that: IndentWidth): Boolean = this match
16481655
case Run(ch1, n1) =>
1649-
that match {
1656+
that match
16501657
case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0)
16511658
case Conc(l, r) => this <= l
1652-
}
1659+
case Max => true
16531660
case Conc(l1, r1) =>
16541661
that match {
16551662
case Conc(l2, r2) => l1 == l2 && r1 <= r2
1663+
case Max => true
16561664
case _ => false
16571665
}
1658-
}
1666+
case Max =>
1667+
that == Max
16591668

16601669
def < (that: IndentWidth): Boolean = this <= that && !(that <= this)
16611670

16621671
def toPrefix: String = this match {
16631672
case Run(ch, n) => ch.toString * n
16641673
case Conc(l, r) => l.toPrefix ++ r.toPrefix
1674+
case Max => "(max >>)"
16651675
}
16661676

16671677
override def toString: String = {
@@ -1673,6 +1683,7 @@ object Scanners {
16731683
this match {
16741684
case Run(ch, n) => s"$n ${kind(ch)}${if (n == 1) "" else "s"}"
16751685
case Conc(l, r) => s"$l, $r"
1686+
case Max => "(max >>)"
16761687
}
16771688
}
16781689
}

docs/_docs/internals/syntax.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,14 @@ 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
264+
| <silent-indent> (CaseClauses | Block) outdent -- silent-indent is inserted by Lexer if no real indent is found
264265
ExprsInParens ::= ExprInParens {‘,’ ExprInParens}
265266
ExprInParens ::= PostfixExpr ‘:’ Type -- normal Expr allows only RefinedType here
266267
| Expr

tests/neg/closure-args.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
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 d = xs
12+
.map: x => x.toString + xs.dropWhile:
13+
y => y > 0 // error
14+

tests/neg/i7751.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import language.experimental.fewerBraces
2-
val a = Some(a=a,)=> // error // error // error
2+
val a = Some(a=a,)=> // error // error
33
val a = Some(x=y,)=>

tests/pos/closure-args.scala

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
import language.experimental.fewerBraces
22

3-
val xs = List(1, 2, 3)
4-
val ys = xs.map x =>
5-
x + 1
6-
val x = ys.foldLeft(0) (x, y) =>
7-
x + y
8-
val y = ys.foldLeft(0) (x: Int, y: Int) =>
9-
val z = x + y
10-
z * z
11-
val as: Int = xs
12-
.map x =>
13-
x * x
14-
.filter y =>
15-
y > 0
16-
(0)
3+
object Test1:
4+
val xs = List(1, 2, 3)
5+
val ys = xs.map: x =>
6+
x + 1
7+
val x = ys.foldLeft(0): (x, y) =>
8+
x + y
9+
val y = ys.foldLeft(0): (x: Int, y: Int) =>
10+
val z = x + y
11+
z * z
12+
val a: Int = xs
13+
.map: x =>
14+
x * x
15+
.filter: (y: Int) =>
16+
y > 0
17+
(0)
18+
val b: Int = xs
19+
.map: x => x * x
20+
.filter: y => y > 0
21+
(0)
22+
val c = List(xs.map: y => y + y)
23+
val d: String = xs
24+
.map: x => x.toString + xs.dropWhile: y => y > 0
25+
.filter: z => !z.isEmpty
26+
(0)
27+
val e = xs.map:
28+
case 1 => 2
29+
case 2 => 3
30+
case x => x
31+
.filter: x => x > 0
32+
val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1
33+
34+
extension (xs: List[Int]) def foo(f: [X] => X => X) = ()
35+
36+
val p = xs.foo: [X] => (x: X) => x
37+
38+
val q = (x: String => String) => x

0 commit comments

Comments
 (0)