Skip to content

Commit b7ab844

Browse files
authored
Merge pull request #14877 from dotty-staging/fix-12554
Clarify and test rules for newline suppression
2 parents d6f268d + df39d8f commit b7ab844

File tree

5 files changed

+116
-9
lines changed

5 files changed

+116
-9
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ object Parsers {
172172
class Parser(source: SourceFile)(using Context) extends ParserCommon(source) {
173173

174174
val in: Scanner = new Scanner(source)
175-
//in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting
175+
// in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting
176176

177177
/** This is the general parse entry point.
178178
* Overridden by ScriptParser

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,6 @@ object Scanners {
469469
true
470470
}
471471

472-
def isContinuing(lastToken: Token) =
473-
(openParensTokens.contains(token) || lastToken == RETURN)
474-
&& !pastBlankLine
475-
&& !migrateTo3
476-
&& !noindentSyntax
477-
478472
/** The indentation width of the given offset. */
479473
def indentWidth(offset: Offset): IndentWidth =
480474
import IndentWidth.{Run, Conc}
@@ -565,6 +559,25 @@ object Scanners {
565559
handler(r)
566560
case _ =>
567561

562+
/** Is this line seen as a continuation of last line? We assume that
563+
* - last line ended in a token that can end a statement
564+
* - current line starts with a token that can start a statement
565+
* - current line does not start with a leading infix operator
566+
* The answer is different for Scala-2 and Scala-3.
567+
* - In Scala 2: Only `{` is treated as continuing, irrespective of indentation.
568+
* But this is in fact handled by Parser.argumentStart which skips a NEWLINE,
569+
* so we always assume false here.
570+
* - In Scala 3: Only indented statements are treated as continuing, as long as
571+
* they start with `(`, `[` or `{`, or the last statement ends in a `return`.
572+
* The Scala 2 rules apply under source `3.0-migration` or under `-no-indent`.
573+
*/
574+
def isContinuing =
575+
lastWidth < nextWidth
576+
&& (openParensTokens.contains(token) || lastToken == RETURN)
577+
&& !pastBlankLine
578+
&& !migrateTo3
579+
&& !noindentSyntax
580+
568581
currentRegion match
569582
case r: Indented =>
570583
indentIsSignificant = indentSyntax
@@ -582,7 +595,7 @@ object Scanners {
582595
&& canEndStatTokens.contains(lastToken)
583596
&& canStartStatTokens.contains(token)
584597
&& !isLeadingInfixOperator(nextWidth)
585-
&& !(lastWidth < nextWidth && isContinuing(lastToken))
598+
&& !isContinuing
586599
then
587600
insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset)
588601
else if indentIsSignificant then

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,38 @@ case 5 => print("V")
243243
println(".")
244244
```
245245

246+
### Using Indentation to Signal Statement Continuation
247+
248+
Indentation is used in some situations to decide whether to insert a virtual semicolon between
249+
two consecutive lines or to treat them as one statement. Virtual semicolon insertion is
250+
suppressed if the second line is indented more relative to the first one, and either the second line
251+
starts with "`(`", "`[`", or "`{`" or the first line ends with `return`. Examples:
252+
253+
```scala
254+
f(x + 1)
255+
(2, 3) // equivalent to `f(x + 1)(2, 3)`
256+
257+
g(x + 1)
258+
(2, 3) // equivalent to `g(x + 1); (2, 3)`
259+
260+
h(x + 1)
261+
{} // equivalent to `h(x + 1){}`
262+
263+
i(x + 1)
264+
{} // equivalent to `i(x + 1); {}`
265+
266+
if x < 0 then return
267+
a + b // equivalent to `if x < 0 then return a + b`
268+
269+
if x < 0 then return
270+
println(a + b) // equivalent to `if x < 0 then return; println(a + b)`
271+
```
272+
In Scala 2, a line starting with "`{`" always continues the function call on the preceding line,
273+
irrespective of indentation, whereas a virtual semicolon is inserted in all other cases.
274+
The Scala-2 behavior is retained under source `-no-indent` or `-source 3.0-migration`.
275+
276+
277+
246278
### The End Marker
247279

248280
Indentation-based syntax has many advantages over other conventions. But one possible problem is that it makes it hard to discern when a large indentation region ends, since there is no specific token that delineates the end. Braces are not much better since a brace by itself also contains no information about what region is closed.
@@ -404,7 +436,7 @@ end IndentWidth
404436

405437
### Settings and Rewrites
406438

407-
Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning.
439+
Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-source 3.0-migration`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning.
408440

409441
The Scala 3 compiler can rewrite source code to indented code and back.
410442
When invoked with options `-rewrite -indent` it will rewrite braces to

tests/neg/i12554a.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
object Test {
2+
def f(s: String) = s
3+
4+
def g: (Int, Int) = {
5+
f("Foo")
6+
(1, 2) // error, ok in Scala 2
7+
}
8+
def g2: (Int, Int) = {
9+
f("Foo")
10+
(1, 2) // ok, ok in Scala 2
11+
}
12+
13+
def h: Unit = {
14+
f("Foo")
15+
{} // error, error in Scala 2
16+
}
17+
18+
def i: Unit = {
19+
f("Foo")
20+
{} // ok, error in Scala 2
21+
}
22+
23+
def j: Int = {
24+
return // error, error in Scala 2
25+
1 + 2
26+
}
27+
28+
def k: Int = {
29+
return // ok, error in Scala 2
30+
1 + 2
31+
}
32+
}

tests/neg/i12554b.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import language.`3.0-migration` // behavior should be the same as for Scala-2
2+
object Test {
3+
4+
def f(s: String) = s
5+
6+
def g: (Int, Int) = {
7+
f("Foo")
8+
(1, 2) // ok
9+
}
10+
11+
def h: Unit = {
12+
f("Foo")
13+
{} // error
14+
}
15+
16+
def i: Unit = {
17+
f("Foo")
18+
{} // error
19+
}
20+
21+
def j: Int = {
22+
return // error
23+
1 + 2
24+
}
25+
26+
def k: Int = {
27+
return // error
28+
1 + 2
29+
}
30+
}

0 commit comments

Comments
 (0)