Skip to content

Commit 135500a

Browse files
authored
Merge pull request #15273 from dotty-staging/change-fewerbraces-1
Change fewerbraces to always use a colon, even before lambdas
2 parents e05af52 + 143a0aa commit 135500a

30 files changed

+554
-340
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ object JavaScanners {
179179
nextChar()
180180

181181
case ':' =>
182-
token = COLON
182+
token = COLONop
183183
nextChar()
184184

185185
case '@' =>

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

+177-105
Large diffs are not rendered by default.

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

+47-30
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,8 @@ object Scanners {
7575
def isNestedStart = token == LBRACE || token == INDENT
7676
def isNestedEnd = token == RBRACE || token == OUTDENT
7777

78-
/** Is token a COLON, after having converted COLONEOL to COLON?
79-
* The conversion means that indentation is not significant after `:`
80-
* anymore. So, warning: this is a side-effecting operation.
81-
*/
82-
def isColon() =
83-
if token == COLONEOL then token = COLON
84-
token == COLON
78+
def isColon =
79+
token == COLONop || token == COLONfollow || token == COLONeol
8580

8681
/** Is current token first one after a newline? */
8782
def isAfterLineEnd: Boolean = lineOffset >= 0
@@ -189,7 +184,10 @@ object Scanners {
189184
val indentSyntax =
190185
((if (Config.defaultIndent) !noindentSyntax else ctx.settings.indent.value)
191186
|| rewriteNoIndent)
192-
&& !isInstanceOf[LookaheadScanner]
187+
&& { this match
188+
case self: LookaheadScanner => self.allowIndent
189+
case _ => true
190+
}
193191

194192
if (rewrite) {
195193
val s = ctx.settings
@@ -206,12 +204,22 @@ object Scanners {
206204
def featureEnabled(name: TermName) = Feature.enabled(name)(using languageImportContext)
207205
def erasedEnabled = featureEnabled(Feature.erasedDefinitions)
208206

207+
private inline val fewerBracesByDefault = false
208+
// turn on to study impact on codebase if `fewerBraces` was the default
209+
209210
private var fewerBracesEnabledCache = false
210211
private var fewerBracesEnabledCtx: Context = NoContext
211212

212213
def fewerBracesEnabled =
213214
if fewerBracesEnabledCtx ne myLanguageImportContext then
214-
fewerBracesEnabledCache = featureEnabled(Feature.fewerBraces)
215+
fewerBracesEnabledCache =
216+
featureEnabled(Feature.fewerBraces)
217+
|| fewerBracesByDefault && indentSyntax && !migrateTo3
218+
// ensure that fewer braces is not the default for 3.0-migration since
219+
// { x: T =>
220+
// expr
221+
// }
222+
// would be ambiguous
215223
fewerBracesEnabledCtx = myLanguageImportContext
216224
fewerBracesEnabledCache
217225

@@ -386,10 +394,11 @@ object Scanners {
386394
*/
387395
def nextToken(): Unit =
388396
val lastToken = token
397+
val lastName = name
389398
adjustSepRegions(lastToken)
390399
getNextToken(lastToken)
391400
if isAfterLineEnd then handleNewLine(lastToken)
392-
postProcessToken()
401+
postProcessToken(lastToken, lastName)
393402
printState()
394403

395404
final def printState() =
@@ -420,7 +429,7 @@ object Scanners {
420429
&& {
421430
// Is current lexeme assumed to start an expression?
422431
// This is the case if the lexime is one of the tokens that
423-
// starts an expression or it is a COLONEOL. Furthermore, if
432+
// starts an expression or it is a COLONeol. Furthermore, if
424433
// the previous token is in backticks, the lexeme may not be a binary operator.
425434
// I.e. in
426435
//
@@ -431,7 +440,7 @@ object Scanners {
431440
// in backticks and is a binary operator. Hence, `x` is not classified as a
432441
// leading infix operator.
433442
def assumeStartsExpr(lexeme: TokenData) =
434-
(canStartExprTokens.contains(lexeme.token) || lexeme.token == COLONEOL)
443+
(canStartExprTokens.contains(lexeme.token) || lexeme.token == COLONeol)
435444
&& (!lexeme.isOperator || nme.raw.isUnary(lexeme.name))
436445
val lookahead = LookaheadScanner()
437446
lookahead.allowLeadingInfixOperators = false
@@ -607,12 +616,11 @@ object Scanners {
607616
currentRegion match
608617
case r: Indented =>
609618
insert(OUTDENT, offset)
610-
if next.token != COLON then
611-
handleNewIndentWidth(r.enclosing, ir =>
612-
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"""))
619+
handleNewIndentWidth(r.enclosing, ir =>
620+
errorButContinue(
621+
i"""The start of this line does not match any of the previous indentation widths.
622+
|Indentation width of current line : $nextWidth
623+
|This falls between previous widths: ${ir.width} and $lastWidth"""))
616624
case r =>
617625
if skipping then
618626
if r.enclosing.isClosedByUndentAt(nextWidth) then
@@ -629,7 +637,7 @@ object Scanners {
629637
currentRegion.knownWidth = nextWidth
630638
else if (lastWidth != nextWidth)
631639
errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
632-
if token != OUTDENT || next.token == COLON then
640+
if token != OUTDENT then
633641
handleNewIndentWidth(currentRegion, _.otherIndentWidths += nextWidth)
634642
end handleNewLine
635643

@@ -638,19 +646,24 @@ object Scanners {
638646
|Previous indent : $lastWidth
639647
|Latest indent : $nextWidth"""
640648

641-
def observeColonEOL(): Unit =
642-
if token == COLON then
649+
def observeColonEOL(inTemplate: Boolean): Unit =
650+
val enabled =
651+
if token == COLONop && inTemplate then
652+
report.deprecationWarning(em"`:` after symbolic operator is deprecated; use backticks around operator instead", sourcePos(offset))
653+
true
654+
else token == COLONfollow && (inTemplate || fewerBracesEnabled)
655+
if enabled then
643656
lookAhead()
644657
val atEOL = isAfterLineEnd || token == EOF
645658
reset()
646-
if atEOL then token = COLONEOL
659+
if atEOL then token = COLONeol
647660

648661
def observeIndented(): Unit =
649662
if indentSyntax && isNewLine then
650663
val nextWidth = indentWidth(next.offset)
651664
val lastWidth = currentRegion.indentWidth
652665
if lastWidth < nextWidth then
653-
currentRegion = Indented(nextWidth, COLONEOL, currentRegion)
666+
currentRegion = Indented(nextWidth, COLONeol, currentRegion)
654667
offset = next.offset
655668
token = INDENT
656669
end observeIndented
@@ -683,10 +696,10 @@ object Scanners {
683696
case _ =>
684697

685698
/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT
686-
* SEMI + ELSE => ELSE, COLON + <EOL> => COLONEOL
699+
* SEMI + ELSE => ELSE, COLON following id/)/] => COLONfollow
687700
* - Insert missing OUTDENTs at EOF
688701
*/
689-
def postProcessToken(): Unit = {
702+
def postProcessToken(lastToken: Token, lastName: SimpleName): Unit = {
690703
def fuse(tok: Int) = {
691704
token = tok
692705
offset = prev.offset
@@ -721,8 +734,10 @@ object Scanners {
721734
reset()
722735
case END =>
723736
if !isEndMarker then token = IDENTIFIER
724-
case COLON =>
725-
if fewerBracesEnabled then observeColonEOL()
737+
case COLONop =>
738+
if lastToken == IDENTIFIER && lastName != null && isIdentifierStart(lastName.head)
739+
|| colonEOLPredecessors.contains(lastToken)
740+
then token = COLONfollow
726741
case RBRACE | RPAREN | RBRACKET =>
727742
closeIndented()
728743
case EOF =>
@@ -1067,7 +1082,7 @@ object Scanners {
10671082
reset()
10681083
next
10691084

1070-
class LookaheadScanner() extends Scanner(source, offset) {
1085+
class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset) {
10711086
override def languageImportContext = Scanner.this.languageImportContext
10721087
}
10731088

@@ -1179,7 +1194,7 @@ object Scanners {
11791194
isSoftModifier && inModifierPosition()
11801195

11811196
def isSoftModifierInParamModifierPosition: Boolean =
1182-
isSoftModifier && lookahead.token != COLON
1197+
isSoftModifier && !lookahead.isColon
11831198

11841199
def isErased: Boolean = isIdent(nme.erased) && erasedEnabled
11851200

@@ -1518,7 +1533,9 @@ object Scanners {
15181533
case NEWLINE => ";"
15191534
case NEWLINES => ";;"
15201535
case COMMA => ","
1521-
case _ => showToken(token)
1536+
case COLONfollow | COLONeol => "':'"
1537+
case _ =>
1538+
if debugTokenStream then showTokenDetailed(token) else showToken(token)
15221539
}
15231540

15241541
/* Resume normal scanning after XML */

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

+17-12
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ abstract class TokensCommon {
1616

1717
def tokenRange(lo: Int, hi: Int): TokenSet = BitSet(lo to hi: _*)
1818

19-
def showTokenDetailed(token: Int): String = debugString(token)
20-
21-
def showToken(token: Int): String = {
22-
val str = tokenString(token)
23-
if (isKeyword(token)) s"'$str'" else str
24-
}
25-
2619
val tokenString, debugString: Array[String] = new Array[String](maxToken + 1)
2720

2821
def enter(token: Int, str: String, debugStr: String = ""): Unit = {
@@ -107,7 +100,7 @@ abstract class TokensCommon {
107100

108101
/** special keywords */
109102
//inline val USCORE = 73; enter(USCORE, "_")
110-
inline val COLON = 74; enter(COLON, ":")
103+
inline val COLONop = 74; enter(COLONop, ":") // a stand-alone `:`, see also COLONfollow
111104
inline val EQUALS = 75; enter(EQUALS, "=")
112105
//inline val LARROW = 76; enter(LARROW, "<-")
113106
//inline val ARROW = 77; enter(ARROW, "=>")
@@ -204,8 +197,11 @@ object Tokens extends TokensCommon {
204197

205198
inline val QUOTE = 87; enter(QUOTE, "'")
206199

207-
inline val COLONEOL = 88; enter(COLONEOL, ":", ": at eol")
208-
inline val SELFARROW = 89; enter(SELFARROW, "=>") // reclassified ARROW following self-type
200+
inline val COLONfollow = 88; enter(COLONfollow, ":")
201+
// A `:` following an alphanumeric identifier or one of the tokens in colonEOLPredecessors
202+
inline val COLONeol = 89; enter(COLONeol, ":", ": at eol")
203+
// A `:` recognized as starting an indentation block
204+
inline val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type
209205

210206
/** XML mode */
211207
inline val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
@@ -233,7 +229,7 @@ object Tokens extends TokensCommon {
233229
final val canStartExprTokens2: TokenSet = canStartExprTokens3 | BitSet(DO)
234230

235231
final val canStartTypeTokens: TokenSet = literalTokens | identifierTokens | BitSet(
236-
THIS, SUPER, USCORE, LPAREN, AT)
232+
THIS, SUPER, USCORE, LPAREN, LBRACE, AT)
237233

238234
final val templateIntroTokens: TokenSet = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT)
239235

@@ -276,7 +272,7 @@ object Tokens extends TokensCommon {
276272
final val closingRegionTokens = BitSet(RBRACE, RPAREN, RBRACKET, CASE) | statCtdTokens
277273

278274
final val canStartIndentTokens: BitSet =
279-
statCtdTokens | BitSet(COLONEOL, WITH, EQUALS, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW, RETURN)
275+
statCtdTokens | BitSet(COLONeol, WITH, EQUALS, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW, RETURN)
280276

281277
/** Faced with the choice between a type and a formal parameter, the following
282278
* tokens determine it's a formal parameter.
@@ -287,7 +283,16 @@ object Tokens extends TokensCommon {
287283

288284
final val endMarkerTokens = identifierTokens | BitSet(IF, WHILE, FOR, MATCH, TRY, NEW, THROW, GIVEN, VAL, THIS)
289285

286+
final val colonEOLPredecessors = BitSet(RPAREN, RBRACKET, BACKQUOTED_IDENT, THIS, SUPER, NEW)
287+
290288
final val closingParens = BitSet(RPAREN, RBRACKET, RBRACE)
291289

292290
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
291+
292+
def showTokenDetailed(token: Int): String = debugString(token)
293+
294+
def showToken(token: Int): String = {
295+
val str = tokenString(token)
296+
if isKeyword(token) || token == COLONfollow || token == COLONeol then s"'$str'" else str
297+
}
293298
}

docs/_docs/internals/syntax.md

+31-13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ layout: doc-page
33
title: "Scala 3 Syntax Summary"
44
---
55

6+
<!--
7+
8+
This page has a companion page at _docs/reference/syntax.md.
9+
10+
!! Make sure to edit both pages in sync. !!
11+
12+
reference/syntax.md shows the official Scala 3 syntax, without deprecated or experimental features.
13+
14+
internals/syntax.md shows the Scala 3 syntax as supported by the parser, including
15+
deprecated and experimental features. It also gives some indications how
16+
productions map to AST nodes.
17+
18+
-->
19+
620
The following description of Scala tokens uses literal characters `‘c’` when
721
referring to the ASCII fragment `\u0000``\u007F`.
822

@@ -88,22 +102,24 @@ nl ::= “new line character”
88102
semi ::= ‘;’ | nl {nl}
89103
```
90104

91-
92105
## Optional Braces
93106

94107
The lexical analyzer also inserts `indent` and `outdent` tokens that represent regions of indented code [at certain points](../reference/other-new-features/indentation.md)
95108

96109
In the context-free productions below we use the notation `<<< ts >>>`
97110
to indicate a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent`. Analogously, the
98111
notation `:<<< ts >>>` indicates a token sequence `ts` that is either enclosed in a pair of braces `{ ts }` or that constitutes an indented region `indent ts outdent` that follows
99-
a `:` at the end of a line.
112+
a `colon` token.
100113

114+
A `colon` token reads as the standard colon "`:`" but is generated instead of it where `colon` is legal according to the context free syntax, but only if the previous token
115+
is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, `new`, "`)`", and "`]`".
101116

102117
```
118+
colon ::= ':' -- with side conditions explained above
103119
<<< ts >>> ::= ‘{’ ts ‘}’
104120
| indent ts outdent
105121
:<<< ts >>> ::= [nl] ‘{’ ts ‘}’
106-
| `:` indent ts outdent
122+
| colon indent ts outdent
107123
```
108124

109125
## Keywords
@@ -124,7 +140,7 @@ type val var while with yield
124140
### Soft keywords
125141

126142
```
127-
as derives end extension infix inline opaque open transparent using | * + -
143+
as derives end extension infix inline opaque open throws transparent using | * + -
128144
```
129145

130146
See the [separate section on soft keywords](../reference/soft-modifier.md) for additional
@@ -197,7 +213,7 @@ FunArgTypes ::= FunArgType { ‘,’ FunArgType }
197213
ParamType ::= [‘=>’] ParamValueType
198214
ParamValueType ::= Type [‘*’] PostfixOp(t, "*")
199215
TypeArgs ::= ‘[’ Types ‘]’ ts
200-
Refinement ::= ‘{’ [RefineDcl] {semi [RefineDcl]} ‘}’ ds
216+
Refinement ::= :<<< [RefineDcl] {semi [RefineDcl]} >>> ds
201217
TypeBounds ::= [‘>:’ Type] [‘<:’ Type] TypeBoundsTree(lo, hi)
202218
TypeParamBounds ::= TypeBounds {‘:’ Type} ContextBounds(typeBounds, tps)
203219
Types ::= Type {‘,’ Type}
@@ -231,13 +247,13 @@ Expr1 ::= [‘inline’] ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[
231247
Ascription ::= ‘:’ InfixType Typed(expr, tp)
232248
| ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*)
233249
Catches ::= ‘catch’ (Expr | ExprCaseClause)
234-
PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op)
250+
PostfixExpr ::= InfixExpr [id] PostfixOp(expr, op) -- only if language.postfixOperators is enabled
235251
InfixExpr ::= PrefixExpr
236252
| InfixExpr id [nl] InfixExpr InfixOp(expr, op, expr)
237-
| InfixExpr id ‘:’ IndentedExpr
253+
| InfixExpr id ColonArgument
238254
| InfixExpr MatchClause
239255
MatchClause ::= ‘match’ <<< CaseClauses >>> Match(expr, cases)
240-
PrefixExpr ::= [PrefixOperator] SimpleExpr PrefixOp(expr, op)
256+
PrefixExpr ::= [PrefixOperator] SimpleExpr PrefixOp(expr, op)
241257
PrefixOperator ::= ‘-’ | ‘+’ | ‘~’ | ‘!’
242258
SimpleExpr ::= SimpleRef
243259
| Literal
@@ -253,11 +269,13 @@ SimpleExpr ::= SimpleRef
253269
| SimpleExpr ‘.’ MatchClause
254270
| SimpleExpr TypeArgs TypeApply(expr, args)
255271
| SimpleExpr ArgumentExprs Apply(expr, args)
256-
| SimpleExpr ‘:’ IndentedExpr -- under language.experimental.fewerBraces
257-
| SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedExpr -- under language.experimental.fewerBraces
272+
| SimpleExpr ColonArgument -- under language.experimental.fewerBraces
258273
| SimpleExpr ‘_’ PostfixOp(expr, _) (to be dropped)
259-
| XmlExpr -- to be dropped
260-
IndentedExpr ::= indent CaseClauses | Block outdent
274+
| XmlExpr -- to be dropped
275+
ColonArgument ::= colon [LambdaStart]
276+
indent (CaseClauses | Block) outdent
277+
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
278+
| HkTypeParamClause ‘=>’
261279
Quoted ::= ‘'’ ‘{’ Block ‘}’
262280
| ‘'’ ‘[’ Type ‘]’
263281
ExprSplice ::= spliceId -- if inside quoted block
@@ -300,7 +318,7 @@ TypeCaseClause ::= ‘case’ (InfixType | ‘_’) ‘=>’ Type [semi]
300318
301319
Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats)
302320
Pattern1 ::= Pattern2 [‘:’ RefinedType] Bind(name, Typed(Ident(wildcard), tpe))
303-
Pattern2 ::= [id ‘@’] InfixPattern Bind(name, pat)
321+
Pattern2 ::= [id ‘@’] InfixPattern [‘*’] Bind(name, pat)
304322
InfixPattern ::= SimplePattern { id [nl] SimplePattern } InfixOp(pat, op, pat)
305323
SimplePattern ::= PatVar Ident(wildcard)
306324
| Literal Bind(name, Ident(wildcard))

0 commit comments

Comments
 (0)