Skip to content

Commit 060a3ec

Browse files
authored
Merge pull request #8659 from dotty-staging/change-indent-parens
Allow indentation inside `(...)`
2 parents ac216c7 + a69c010 commit 060a3ec

File tree

7 files changed

+133
-101
lines changed

7 files changed

+133
-101
lines changed

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -650,12 +650,11 @@ object Parsers {
650650

651651
/** Check that this is not the start of a statement that's indented relative to the current region.
652652
*/
653-
def checkNextNotIndented(): Unit = in.currentRegion match
654-
case r: IndentSignificantRegion if in.isNewLine =>
653+
def checkNextNotIndented(): Unit =
654+
if in.isNewLine then
655655
val nextIndentWidth = in.indentWidth(in.next.offset)
656-
if r.indentWidth < nextIndentWidth then
656+
if in.currentRegion.indentWidth < nextIndentWidth then
657657
warning(i"Line is indented too far to the right, or a `{` or `:` is missing", in.next.offset)
658-
case _ =>
659658

660659
/* -------- REWRITES ----------------------------------------------------------- */
661660

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

Lines changed: 52 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ object Scanners {
289289
/** Are we in a `${ }` block? such that RBRACE exits back into multiline string. */
290290
private def inMultiLineInterpolatedExpression =
291291
currentRegion match {
292-
case InBraces(_, InString(true, _)) => true
292+
case InBraces(InString(true, _)) => true
293293
case _ => false
294294
}
295295

@@ -305,7 +305,7 @@ object Scanners {
305305
case LPAREN | LBRACKET =>
306306
currentRegion = InParens(lastToken, currentRegion)
307307
case LBRACE =>
308-
currentRegion = InBraces(null, currentRegion)
308+
currentRegion = InBraces(currentRegion)
309309
case RBRACE =>
310310
def dropBraces(): Unit = currentRegion match {
311311
case r: InBraces =>
@@ -340,7 +340,7 @@ object Scanners {
340340
if (next.token == EMPTY) {
341341
lastOffset = lastCharOffset
342342
currentRegion match {
343-
case InString(multiLine, _) => fetchStringPart(multiLine)
343+
case InString(multiLine, _) if lastToken != STRINGPART => fetchStringPart(multiLine)
344344
case _ => fetchToken()
345345
}
346346
if (token == ERROR) adjustSepRegions(STRINGLIT) // make sure we exit enclosing string literal
@@ -360,6 +360,7 @@ object Scanners {
360360

361361
/** Insert `token` at assumed `offset` in front of current one. */
362362
def insert(token: Token, offset: Int) = {
363+
assert(next.token == EMPTY, next)
363364
next.copyFrom(this)
364365
this.offset = offset
365366
this.token = token
@@ -518,26 +519,24 @@ object Scanners {
518519
* I.e. `a <= b` iff `b.startsWith(a)`. If indentation is significant it is considered an error
519520
* if the current indentation width and the indentation of the current token are incomparable.
520521
*/
521-
def handleNewLine(lastToken: Token) = {
522+
def handleNewLine(lastToken: Token) =
522523
var indentIsSignificant = false
523524
var newlineIsSeparating = false
524525
var lastWidth = IndentWidth.Zero
525526
var indentPrefix = EMPTY
526527
val nextWidth = indentWidth(offset)
527-
currentRegion match {
528+
currentRegion match
528529
case r: Indented =>
529530
indentIsSignificant = indentSyntax
530531
lastWidth = r.width
531532
newlineIsSeparating = lastWidth <= nextWidth || r.isOutermost
532533
indentPrefix = r.prefix
533-
case r: InBraces =>
534+
case r =>
534535
indentIsSignificant = indentSyntax
535-
if (r.width == null) r.width = nextWidth
536-
lastWidth = r.width
537-
newlineIsSeparating = true
538-
indentPrefix = LBRACE
539-
case _ =>
540-
}
536+
if (r.knownWidth == null) r.knownWidth = nextWidth
537+
lastWidth = r.knownWidth
538+
newlineIsSeparating = r.isInstanceOf[InBraces]
539+
541540
if newlineIsSeparating
542541
&& canEndStatTokens.contains(lastToken)
543542
&& canStartStatTokens.contains(token)
@@ -580,7 +579,7 @@ object Scanners {
580579
currentRegion = Indented(curWidth, others + nextWidth, prefix, outer)
581580
case _ =>
582581
}
583-
}
582+
end handleNewLine
584583

585584
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth) =
586585
i"""Incompatible combinations of tabs and spaces in indentation prefixes.
@@ -597,10 +596,7 @@ object Scanners {
597596
def observeIndented(): Unit =
598597
if indentSyntax && isNewLine then
599598
val nextWidth = indentWidth(next.offset)
600-
val lastWidth = currentRegion match
601-
case r: IndentSignificantRegion => r.indentWidth
602-
case _ => nextWidth
603-
599+
val lastWidth = currentRegion.indentWidth
604600
if lastWidth < nextWidth then
605601
currentRegion = Indented(nextWidth, Set(), COLONEOL, currentRegion)
606602
offset = next.offset
@@ -650,15 +646,27 @@ object Scanners {
650646
lookahead()
651647
if (token != ELSE) reset()
652648
case COMMA =>
653-
lookahead()
654-
if (isAfterLineEnd && (token == RPAREN || token == RBRACKET || token == RBRACE || token == OUTDENT)) {
655-
/* skip the trailing comma */
656-
} else if (token == EOF) { // e.g. when the REPL is parsing "val List(x, y, _*,"
657-
/* skip the trailing comma */
658-
} else reset()
649+
def isEnclosedInParens(r: Region): Boolean = r match
650+
case r: Indented => isEnclosedInParens(r.outer)
651+
case _: InParens => true
652+
case _ => false
653+
currentRegion match
654+
case r: Indented if isEnclosedInParens(r.outer) =>
655+
insert(OUTDENT, offset)
656+
currentRegion = r.outer
657+
case _ =>
658+
lookahead()
659+
if isAfterLineEnd
660+
&& (token == RPAREN || token == RBRACKET || token == RBRACE || token == OUTDENT)
661+
then
662+
() /* skip the trailing comma */
663+
else if token == EOF then // e.g. when the REPL is parsing "val List(x, y, _*,"
664+
() /* skip the trailing comma */
665+
else
666+
reset()
659667
case COLON =>
660668
if colonSyntax then observeColonEOL()
661-
case EOF | RBRACE =>
669+
case EOF | RBRACE | RPAREN | RBRACKET =>
662670
currentRegion match {
663671
case r: Indented if !r.isOutermost =>
664672
insert(OUTDENT, offset)
@@ -1097,13 +1105,7 @@ object Scanners {
10971105
getRawStringLit()
10981106
}
10991107

1100-
@annotation.tailrec private def getStringPart(multiLine: Boolean): Unit = {
1101-
def finishStringPart() = {
1102-
setStrVal()
1103-
token = STRINGPART
1104-
next.lastOffset = charOffset - 1
1105-
next.offset = charOffset - 1
1106-
}
1108+
@annotation.tailrec private def getStringPart(multiLine: Boolean): Unit =
11071109
if (ch == '"')
11081110
if (multiLine) {
11091111
nextRawChar()
@@ -1127,18 +1129,19 @@ object Scanners {
11271129
getStringPart(multiLine)
11281130
}
11291131
else if (ch == '{') {
1130-
finishStringPart()
1131-
nextRawChar()
1132-
next.token = LBRACE
1132+
setStrVal()
1133+
token = STRINGPART
11331134
}
11341135
else if (Character.isUnicodeIdentifierStart(ch) || ch == '_') {
1135-
finishStringPart()
1136-
while ({
1136+
setStrVal()
1137+
token = STRINGPART
1138+
next.lastOffset = charOffset - 1
1139+
next.offset = charOffset - 1
1140+
while
11371141
putChar(ch)
11381142
nextRawChar()
11391143
ch != SU && Character.isUnicodeIdentifierPart(ch)
1140-
})
1141-
()
1144+
do ()
11421145
finishNamed(target = next)
11431146
}
11441147
else
@@ -1157,7 +1160,7 @@ object Scanners {
11571160
getStringPart(multiLine)
11581161
}
11591162
}
1160-
}
1163+
end getStringPart
11611164

11621165
private def fetchStringPart(multiLine: Boolean) = {
11631166
offset = charOffset - 1
@@ -1379,7 +1382,7 @@ object Scanners {
13791382
* InBraces a pair of braces { ... }
13801383
* Indented a pair of <indent> ... <outdent> tokens
13811384
*/
1382-
abstract class Region {
1385+
abstract class Region:
13831386
/** The region enclosing this one, or `null` for the outermost region */
13841387
def outer: Region | Null
13851388

@@ -1389,32 +1392,24 @@ object Scanners {
13891392
/** The enclosing region, which is required to exist */
13901393
def enclosing: Region = outer.asInstanceOf[Region]
13911394

1392-
/** If this is an InBraces or Indented region, its indentation width, or Zero otherwise */
1393-
def indentWidth: IndentWidth = IndentWidth.Zero
1394-
}
1395+
var knownWidth: IndentWidth | Null = null
1396+
1397+
/** The indentation width, Zero if not known */
1398+
final def indentWidth: IndentWidth =
1399+
if knownWidth == null then IndentWidth.Zero else knownWidth
1400+
end Region
13951401

13961402
case class InString(multiLine: Boolean, outer: Region) extends Region
13971403
case class InParens(prefix: Token, outer: Region) extends Region
1398-
1399-
abstract class IndentSignificantRegion extends Region
1400-
1401-
case class InBraces(var width: IndentWidth | Null, outer: Region)
1402-
extends IndentSignificantRegion {
1403-
// The indent width starts out as `null` when the opening brace is encountered
1404-
// It is then adjusted when the next token on a new line is encountered.
1405-
override def indentWidth: IndentWidth =
1406-
if width == null then IndentWidth.Zero else width
1407-
}
1404+
case class InBraces(outer: Region) extends Region
14081405

14091406
/** A class describing an indentation region.
14101407
* @param width The principal indendation width
14111408
* @param others Other indendation widths > width of lines in the same region
14121409
* @param prefix The token before the initial <indent> of the region
14131410
*/
1414-
case class Indented(width: IndentWidth, others: Set[IndentWidth], prefix: Token, outer: Region | Null)
1415-
extends IndentSignificantRegion {
1416-
override def indentWidth = width
1417-
}
1411+
case class Indented(width: IndentWidth, others: Set[IndentWidth], prefix: Token, outer: Region | Null) extends Region:
1412+
knownWidth = width
14181413

14191414
enum IndentWidth {
14201415
case Run(ch: Char, n: Int)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ object Tokens extends TokensCommon {
269269

270270
final val statCtdTokens: BitSet = BitSet(THEN, ELSE, DO, CATCH, FINALLY, YIELD, MATCH)
271271

272-
final val closingRegionTokens = BitSet(RBRACE, CASE) | statCtdTokens
272+
final val closingRegionTokens = BitSet(RBRACE, RPAREN, RBRACKET, CASE) | statCtdTokens
273273

274274
final val canStartIndentTokens: BitSet =
275275
statCtdTokens | BitSet(COLONEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR, IF)

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: Optional Braces
44
---
55

66
As an experimental feature, Scala 3 enforces some rules on indentation and allows
7-
some occurrences of braces `{...}` to be optional.
7+
some occurrences of braces `{...}` to be optional.
88
It can be turned off with the compiler flag `-noindent`.
99

1010
- First, some badly indented programs are flagged with warnings.
@@ -78,10 +78,11 @@ There are two rules:
7878
If the indentation width of the token on the next line is still less than the new current indentation width, step (2) repeats. Therefore, several `<outdent>` tokens
7979
may be inserted in a row.
8080

81-
An `<outdent>` is also inserted if the next statement following a statement sequence starting with an `<indent>` closes an indentation region, i.e. is one of `then`, `else`, `do`, `catch`, `finally`, `yield`, `}` or `case`.
81+
An `<outdent>` is also inserted if the next token following a statement sequence starting with an `<indent>` closes an indentation region, i.e. is one of `then`, `else`, `do`, `catch`, `finally`, `yield`, `}`, `)`, `]` or `case`.
8282

83-
It is an error if the indentation width of the token following an `<outdent>` does not
84-
match the indentation of some previous line in the enclosing indentation region. For instance, the following would be rejected.
83+
An `<outdent>` is finally inserted in front of a comma that follows a statement sequence starting with an `<indent>` if the indented region is itself enclosed in parentheses
84+
85+
It is an error if the indentation width of the token following an `<outdent>` does not match the indentation of some previous line in the enclosing indentation region. For instance, the following would be rejected.
8586
```scala
8687
if x < 0
8788
-x

tests/neg/parser-stability-19.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object x0 {
22
case class x0[](): // error
33
def x0( ) ] // error
4-
def x0 ( x0:x0 ):x0.type = x1 x0 // error // error
4+
def x0 ( x0:x0 ):x0.type = x1 x0 // error // error // error
55
// error

tests/pos/indent-in-parens.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
def f(x: Int) =
2+
assert(
3+
if x > 0 then
4+
true
5+
else
6+
false
7+
)
8+
assert(
9+
if x > 0 then
10+
true
11+
else
12+
false)
13+
assert(
14+
if x > 0 then
15+
true
16+
else
17+
false, "fail")
18+
assert(
19+
if x > 0 then
20+
true
21+
else
22+
if x < 0 then
23+
true
24+
else
25+
false, "fail")
26+
(
27+
if x > 0 then
28+
println(x)
29+
x
30+
else
31+
s"""foo${
32+
if x > 0 then
33+
println(x)
34+
x
35+
else
36+
-x
37+
}"""
38+
)

tests/run/LazyValsLongs.scala

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,40 @@ object Test {
2323
def main(args: Array[String]): Unit = {
2424
val c = new I
2525
import c._
26-
val l1 = List(A1,
27-
A2,
28-
A3,
29-
A4,
30-
A5,
31-
A6,
32-
A7,
33-
A8,
34-
A9,
35-
A10,
36-
A11,
37-
A12,
38-
A13,
39-
A14,
40-
A15,
41-
A16,
42-
A17,
26+
val l1 = List(A1,
27+
A2,
28+
A3,
29+
A4,
30+
A5, A6,
31+
A7,
32+
A8,
33+
A9,
34+
A10,
35+
A11,
36+
A12,
37+
A13,
38+
A14,
39+
A15,
40+
A16,
41+
A17,
4342
A18)
44-
val l2 = List(A1,
45-
A2,
46-
A3,
47-
A4,
48-
A5,
49-
A6,
50-
A7,
51-
A8,
52-
A9,
53-
A10,
54-
A11,
55-
A12,
56-
A13,
57-
A14,
58-
A15,
59-
A16,
60-
A17,
43+
val l2 = List(A1,
44+
A2,
45+
A3,
46+
A4,
47+
A5,
48+
A6,
49+
A7,
50+
A8,
51+
A9,
52+
A10,
53+
A11,
54+
A12,
55+
A13,
56+
A14,
57+
A15,
58+
A16,
59+
A17,
6160
A18)
6261
assert(l1.mkString == l2.mkString)
6362
assert(!l1.contains(null)) // @odersky - 2.12 encoding seems wonky here as well

0 commit comments

Comments
 (0)