Skip to content

Commit abed6d9

Browse files
authored
Allow lines starting with . to fall outside previous indentation widths (#17056)
If a line following some indented code starts with a '`.`' and its indentation width is different from the indentation widths of the two neighboring regions by more than a single space, the line is accepted even if it does not match a previous indentation width. This tweak of the indentation rules is introduced so that code like the following can be written. ```scala def foo(xs: List[Int]) = xs.map: x => x + 1 .filter: x => x > 0 ```
2 parents 9642fb2 + d836de6 commit abed6d9

File tree

6 files changed

+80
-8
lines changed

6 files changed

+80
-8
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3602,8 +3602,6 @@ object Parsers {
36023602
}
36033603
}
36043604

3605-
3606-
36073605
/** DefDef ::= DefSig [‘:’ Type] ‘=’ Expr
36083606
* | this TypelessClauses [DefImplicitClause] `=' ConstrExpr
36093607
* DefDcl ::= DefSig `:' Type

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -611,11 +611,17 @@ object Scanners {
611611
case r: Indented =>
612612
insert(OUTDENT, offset)
613613
handleNewIndentWidth(r.enclosing, ir =>
614-
val lw = lastWidth
615-
errorButContinue(
616-
em"""The start of this line does not match any of the previous indentation widths.
617-
|Indentation width of current line : $nextWidth
618-
|This falls between previous widths: ${ir.width} and $lw"""))
614+
if next.token == DOT
615+
&& !nextWidth.isClose(r.indentWidth)
616+
&& !nextWidth.isClose(ir.indentWidth)
617+
then
618+
ir.otherIndentWidths += nextWidth
619+
else
620+
val lw = lastWidth
621+
errorButContinue(
622+
em"""The start of this line does not match any of the previous indentation widths.
623+
|Indentation width of current line : $nextWidth
624+
|This falls between previous widths: ${ir.width} and $lw"""))
619625
case r =>
620626
if skipping then
621627
if r.enclosing.isClosedByUndentAt(nextWidth) then
@@ -1666,6 +1672,17 @@ object Scanners {
16661672

16671673
def < (that: IndentWidth): Boolean = this <= that && !(that <= this)
16681674

1675+
/** Does `this` differ from `that` by not more than a single space? */
1676+
def isClose(that: IndentWidth): Boolean = this match
1677+
case Run(ch1, n1) =>
1678+
that match
1679+
case Run(ch2, n2) => ch1 == ch2 && ch1 != '\t' && (n1 - n2).abs <= 1
1680+
case Conc(l, r) => false
1681+
case Conc(l1, r1) =>
1682+
that match
1683+
case Conc(l2, r2) => l1 == l2 && r1.isClose(r2)
1684+
case _ => false
1685+
16691686
def toPrefix: String = this match {
16701687
case Run(ch, n) => ch.toString * n
16711688
case Conc(l, r) => l.toPrefix ++ r.toPrefix

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ There are two rules:
100100

101101
- 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.
102102

103-
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.
103+
It is generally 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.
104104

105105
```scala
106106
if x < 0 then
@@ -109,6 +109,19 @@ if x < 0 then
109109
x
110110
```
111111

112+
However, there is one exception to this rule: If the next line starts with a '`.`' _and_ the indentation
113+
width is different from the indentation widths of the two neighboring regions by more than a single space, the line accepted. For instance, the following is OK:
114+
115+
```scala
116+
xs.map: x =>
117+
x + 1
118+
.filter: x =>
119+
x > 0
120+
```
121+
Here, the line starting with `.filter` does not have an indentation level matching a previous line,
122+
but it is still accepted since it starts with a '`.`' and differs in at least two spaces from the
123+
indentation levels of both the region that is closed and the next outer region.
124+
112125
Indentation tokens are only inserted in regions where newline statement separators are also inferred:
113126
at the top-level, inside braces `{...}`, but not inside parentheses `(...)`, patterns or types.
114127

tests/neg/outdent-dot.check

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Error: tests/neg/outdent-dot.scala:6:5 ------------------------------------------------------------------------------
2+
6 | .toString // error
3+
| ^
4+
| The start of this line does not match any of the previous indentation widths.
5+
| Indentation width of current line : 5 spaces
6+
| This falls between previous widths: 2 spaces and 6 spaces
7+
-- Error: tests/neg/outdent-dot.scala:11:3 -----------------------------------------------------------------------------
8+
11 | .filter: x => // error
9+
| ^
10+
| The start of this line does not match any of the previous indentation widths.
11+
| Indentation width of current line : 3 spaces
12+
| This falls between previous widths: 2 spaces and 6 spaces
13+
-- Error: tests/neg/outdent-dot.scala:13:4 -----------------------------------------------------------------------------
14+
13 | println("foo") // error
15+
| ^
16+
| The start of this line does not match any of the previous indentation widths.
17+
| Indentation width of current line : 4 spaces
18+
| This falls between previous widths: 2 spaces and 6 spaces

tests/neg/outdent-dot.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def Block(f: => Int): Int = f
2+
3+
def bar(): String =
4+
Block:
5+
2 + 2
6+
.toString // error
7+
8+
def foo(xs: List[Int]) =
9+
xs.map: x =>
10+
x + 1
11+
.filter: x => // error
12+
x > 0
13+
println("foo") // error

tests/pos/outdent-dot.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def Block(f: => Int): Int = f
2+
3+
def bar(): String =
4+
Block:
5+
2 + 2
6+
.toString
7+
8+
def foo(xs: List[Int]) =
9+
xs.map: x =>
10+
x + 1
11+
.filter: x =>
12+
x > 0
13+
println("foo")

0 commit comments

Comments
 (0)