Skip to content

Commit 8f82a28

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 90c9a27 commit 8f82a28

File tree

10 files changed

+162
-106
lines changed

10 files changed

+162
-106
lines changed

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

Lines changed: 63 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
@@ -893,9 +894,8 @@ object Parsers {
893894
val next = in.lookahead.token
894895
next == LBRACKET || next == LPAREN
895896

896-
897897
def followingIsSelfType() =
898-
val lookahead = in.LookaheadScanner()
898+
val lookahead = in.LookaheadScanner(allowIndent = true)
899899
lookahead.nextToken()
900900
lookahead.token == COLON
901901
&& {
@@ -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: 30 additions & 11 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

@@ -610,9 +618,13 @@ object Scanners {
610618
if next.token != COLON then
611619
handleNewIndentWidth(r.enclosing, ir =>
612620
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"""))
621+
if r.indentWidth == IndentWidth.Max then
622+
i"""Enclosing expression is nested in a one line lambda expression following `:`.
623+
|It may not spill over to a new line."""
624+
else
625+
i"""The start of this line does not match any of the previous indentation widths.
626+
|Indentation width of current line : $nextWidth
627+
|This falls between previous widths: ${ir.width} and $lastWidth"""))
616628
case r =>
617629
if skipping then
618630
if r.enclosing.isClosedByUndentAt(nextWidth) then
@@ -653,7 +665,9 @@ object Scanners {
653665
currentRegion = Indented(nextWidth, COLONEOL, currentRegion)
654666
offset = next.offset
655667
token = INDENT
656-
end observeIndented
668+
669+
def insertMaxIndent() =
670+
currentRegion = Indented(IndentWidth.Max, ARROW, currentRegion)
657671

658672
/** Insert an <outdent> token if next token closes an indentation region.
659673
* Exception: continue if indentation region belongs to a `match` and next token is `case`.
@@ -1067,7 +1081,7 @@ object Scanners {
10671081
reset()
10681082
next
10691083

1070-
class LookaheadScanner() extends Scanner(source, offset) {
1084+
class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset) {
10711085
override def languageImportContext = Scanner.this.languageImportContext
10721086
}
10731087

@@ -1643,25 +1657,29 @@ object Scanners {
16431657
enum IndentWidth {
16441658
case Run(ch: Char, n: Int)
16451659
case Conc(l: IndentWidth, r: Run)
1660+
case Max // delimits one-line lambdas following a `:`
16461661

1647-
def <= (that: IndentWidth): Boolean = this match {
1662+
def <= (that: IndentWidth): Boolean = this match
16481663
case Run(ch1, n1) =>
1649-
that match {
1664+
that match
16501665
case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0)
16511666
case Conc(l, r) => this <= l
1652-
}
1667+
case Max => true
16531668
case Conc(l1, r1) =>
16541669
that match {
16551670
case Conc(l2, r2) => l1 == l2 && r1 <= r2
1671+
case Max => true
16561672
case _ => false
16571673
}
1658-
}
1674+
case Max =>
1675+
that == Max
16591676

16601677
def < (that: IndentWidth): Boolean = this <= that && !(that <= this)
16611678

16621679
def toPrefix: String = this match {
16631680
case Run(ch, n) => ch.toString * n
16641681
case Conc(l, r) => l.toPrefix ++ r.toPrefix
1682+
case Max => "(max >>)"
16651683
}
16661684

16671685
override def toString: String = {
@@ -1673,6 +1691,7 @@ object Scanners {
16731691
this match {
16741692
case Run(ch, n) => s"$n ${kind(ch)}${if (n == 1) "" else "s"}"
16751693
case Conc(l, r) => s"$l, $r"
1694+
case Max => "(max >>)"
16761695
}
16771696
}
16781697
}

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-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: 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,)=>

0 commit comments

Comments
 (0)