Skip to content

Commit 0dc824c

Browse files
authored
Merge pull request #10969 from dotty-staging/indent-parens.scala
Allow indentation to work inside parens
2 parents fad5b8d + f767c00 commit 0dc824c

File tree

7 files changed

+77
-12
lines changed

7 files changed

+77
-12
lines changed

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,22 +337,23 @@ object Scanners {
337337

338338
/** A leading symbolic or backquoted identifier is treated as an infix operator if
339339
* - it does not follow a blank line, and
340-
* - it is followed on the same line by at least one ' '
341-
* and a token that can start an expression.
340+
* - it is followed by at least one whitespace character and a
341+
* token that can start an expression.
342342
* If a leading infix operator is found and the source version is `3.0-migration`, emit a change warning.
343343
*/
344344
def isLeadingInfixOperator(inConditional: Boolean = true) =
345345
allowLeadingInfixOperators
346346
&& ( token == BACKQUOTED_IDENT
347347
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1)))
348-
&& ch == ' '
348+
&& (isWhitespace(ch) || ch == LF)
349349
&& !pastBlankLine
350350
&& {
351351
val lookahead = LookaheadScanner()
352352
lookahead.allowLeadingInfixOperators = false
353353
// force a NEWLINE a after current token if it is on its own line
354354
lookahead.nextToken()
355355
canStartExprTokens.contains(lookahead.token)
356+
|| lookahead.token == NEWLINE && canStartExprTokens.contains(lookahead.next.token)
356357
}
357358
&& {
358359
if migrateTo3 then
@@ -462,7 +463,7 @@ object Scanners {
462463
indentPrefix = r.prefix
463464
case r =>
464465
indentIsSignificant = indentSyntax
465-
if (r.knownWidth == null) r.knownWidth = nextWidth
466+
r.proposeKnownWidth(nextWidth, lastToken)
466467
lastWidth = r.knownWidth
467468
newlineIsSeparating = r.isInstanceOf[InBraces]
468469

@@ -1349,6 +1350,18 @@ object Scanners {
13491350
/** The indentation width, Zero if not known */
13501351
final def indentWidth: IndentWidth =
13511352
if knownWidth == null then IndentWidth.Zero else knownWidth
1353+
1354+
def proposeKnownWidth(width: IndentWidth, lastToken: Token) =
1355+
if knownWidth == null then
1356+
this match
1357+
case InParens(_, _) if lastToken != LPAREN =>
1358+
useOuterWidth()
1359+
case _ =>
1360+
knownWidth = width
1361+
1362+
private def useOuterWidth(): Unit =
1363+
if enclosing.knownWidth == null then enclosing.useOuterWidth()
1364+
knownWidth = enclosing.knownWidth
13521365
end Region
13531366

13541367
case class InString(multiLine: Boolean, outer: Region) extends Region

docs/docs/reference/changed-features/operators.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ val str = "hello"
118118

119119
def condition =
120120
x > 0
121-
|| xs.exists(_ > 0)
121+
||
122+
xs.exists(_ > 0)
122123
|| xs.isEmpty
123124
```
124125

@@ -129,8 +130,8 @@ To make this syntax work, the rules are modified to not infer semicolons in fron
129130
A _leading infix operator_ is
130131
- a symbolic identifier such as `+`, or `approx_==`, or an identifier in backticks,
131132
- that starts a new line,
132-
- that precedes a token on the same line that can start an expression,
133-
- and that is immediately followed by at least one space character `' '`.
133+
- that precedes a token on the same or the next line that can start an expression,
134+
- and that is immediately followed by at least one whitespace character.
134135

135136
Example:
136137

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,37 @@ Indentation prefixes can consist of spaces and/or tabs. Indentation widths are t
161161

162162
### Indentation and Braces
163163

164-
Indentation can be mixed freely with braces. For interpreting indentation inside braces, the following rules apply.
164+
Indentation can be mixed freely with braces `{...}`, as well as brackets `[...]` and parentheses `(...)`. For interpreting indentation inside such regions, the following rules apply.
165165

166166
1. The assumed indentation width of a multiline region enclosed in braces is the
167167
indentation width of the first token that starts a new line after the opening brace.
168168

169-
2. On encountering a closing brace `}`, as many `<outdent>` tokens as necessary are
170-
inserted to close all open indentation regions inside the pair of braces.
169+
2. The assumed indentation width of a multiline region inside brackets or parentheses is:
170+
171+
- if the opening bracket or parenthesis is at the end of a line, the indentation width of token following it,
172+
- otherwise, the indentation width of the enclosing region.
173+
174+
3. On encountering a closing brace `}`, bracket `]` or parenthesis `)`, as many `<outdent>` tokens as necessary are inserted to close all open nested indentation regions.
175+
176+
For instance, consider:
177+
```scala
178+
{
179+
val x = f(x: Int, y =>
180+
x * (
181+
y + 1
182+
) +
183+
(x +
184+
x)
185+
)
186+
}
187+
```
188+
- Here, the indentation width of the region enclosed by the braces is 3 (i.e. the indentation width of the
189+
statement starting with `val`).
190+
- The indentation width of the region in parentheses that follows `f` is also 3, since the opening
191+
parenthesis is not at the end of a line.
192+
- The indentation width of the region in parentheses around `y + 1` is 9
193+
(i.e. the indentation width of `y + 1`).
194+
- Finally, the indentation width of the last region in parentheses starting with `(x` is 6 (i.e. the indentation width of the indented region following the `=>`.
171195

172196
### Special Treatment of Case Clauses
173197

tests/pos/indent-in-parens.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
def g(x: Int, op: Int => Int) = op(x)
2+
3+
def test1 = g(1, x =>
4+
val y = x * x
5+
y * y
6+
)
7+
8+
def test2 = g(1,
9+
x =>
10+
val y = x * x
11+
y * y
12+
)
13+
114
def f(x: Int) =
215
assert(
316
if x > 0 then

tests/pos/leading-infix-op.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def f(x: Int): Boolean =
2+
x < 0
3+
||
4+
x > 0
5+
&&
6+
x != 3
7+
8+
def g(x: Option[Int]) = x match
9+
case Some(err) =>
10+
println("hi")
11+
???
12+
case None =>
13+
???

tests/run/Course-2002-13.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class Parser(s: String) {
179179
val a = token;
180180
token = it.next;
181181
Con(a,
182-
if (token equals "(") {
182+
if (token equals "(") {
183183
token = it.next;
184184
val ts: List[Term] = if (token equals ")") List() else rep(term);
185185
if (token equals ")") token = it.next else syntaxError("`)` expected");

tests/run/i7031.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
@main def Test = {
22
val a = 5
33
val x = 1
4+
45
+ //
56
`a` * 6
67

7-
assert(x == 1)
8+
assert(x == 1, x)
89
}
910

0 commit comments

Comments
 (0)