Skip to content

Commit 3653fb0

Browse files
committed
Use indentation to control newline insertion before parentheses
Scala currently uses an ad-hoc set of rules to determine whether a line starting with an opening `(`, `[`, or `{` is an argument to the previous line or starts a new statement. Specifically, a new line is always inserted but is then skipped if the next token is a `{`. On the other hand, lines starting with `[` and `(` always start a new statement. This leads to the gotchas that ``` val x= foo { ... } ``` treats the braces of an argument to `foo`. A blank line is required to avoid this. On the other hand, ``` foo(arg1) (arg2) (arg3) ``` does not work; the following lines are interpreted as separate statements. In this PR, indentation is used instead to determine whether a following line starting with `(`, `[`, ir `{` is an argument or a separate statement. In the first example, it is a separate statement, since it is not indented. In the second example, it is treated as an argument, since it is indented. If we are past a blank line, we always assume the newline. For instance, this example from Pouring.scala works: ```scala val moves = val glasses = 0 until capacity.length (for g <- glasses yield Move.Empty(g)) ++ (for g <- glasses yield Move.Fill(g)) ++ (for g1 <- glasses; g2 <- glasses if g1 != g2 yield Move.Pour(g1, g2)) ``` Without the blank line, the `(for g <- ...` expression would be treated as an additional argument to `capacity.length`.
1 parent b3f2aee commit 3653fb0

File tree

9 files changed

+47
-19
lines changed

9 files changed

+47
-19
lines changed

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,10 +1054,9 @@ class TreeUnpickler(reader: TastyReader,
10541054
ConstFold(untpd.Select(qual, name).withType(tpe))
10551055
}
10561056

1057-
def readQualId(): (untpd.Ident, TypeRef) = {
1057+
def readQualId(): (untpd.Ident, TypeRef) =
10581058
val qual = readTerm().asInstanceOf[untpd.Ident]
1059-
(untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef])
1060-
}
1059+
(untpd.Ident(qual.name).withSpan(qual.span), qual.tpe.asInstanceOf[TypeRef])
10611060

10621061
def accessibleDenot(qualType: Type, name: Name, sig: Signature) = {
10631062
val pre = ctx.typeAssigner.maybeSkolemizePrefix(qualType, name)

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

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,11 +1278,6 @@ object Parsers {
12781278
if (in.token == COLONEOL) in.nextToken()
12791279
}
12801280

1281-
def possibleBracesStart(): Unit = {
1282-
colonAtEOLOpt()
1283-
newLineOptWhenFollowedBy(LBRACE)
1284-
}
1285-
12861281
def possibleTemplateStart(isNew: Boolean = false): Unit =
12871282
in.observeColonEOL()
12881283
if in.token == COLONEOL then
@@ -1473,7 +1468,7 @@ object Parsers {
14731468
val refinedType: () => Tree = () => refinedTypeRest(withType())
14741469

14751470
def refinedTypeRest(t: Tree): Tree = {
1476-
possibleBracesStart()
1471+
colonAtEOLOpt()
14771472
if (in.isNestedStart)
14781473
refinedTypeRest(atSpan(startOffset(t)) { RefinedTypeTree(rejectWildcardType(t), refinement()) })
14791474
else t
@@ -2227,7 +2222,7 @@ object Parsers {
22272222
}
22282223

22292224
def simpleExprRest(t: Tree, canApply: Boolean = true): Tree = {
2230-
if (canApply) possibleBracesStart()
2225+
if canApply then colonAtEOLOpt()
22312226
in.token match {
22322227
case DOT =>
22332228
in.nextToken()
@@ -2303,7 +2298,7 @@ object Parsers {
23032298
/** ArgumentExprss ::= {ArgumentExprs}
23042299
*/
23052300
def argumentExprss(fn: Tree): Tree = {
2306-
possibleBracesStart()
2301+
colonAtEOLOpt()
23072302
if (in.token == LPAREN || in.isNestedStart) argumentExprss(mkApply(fn, argumentExprs()))
23082303
else fn
23092304
}
@@ -3328,7 +3323,7 @@ object Parsers {
33283323
*/
33293324
def selfInvocation(): Tree =
33303325
atSpan(accept(THIS)) {
3331-
possibleBracesStart()
3326+
colonAtEOLOpt()
33323327
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
33333328
}
33343329

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ object Scanners {
409409
* and a token that can start an expression.
410410
* If a leading infix operator is found and the source version is `3.0-migration`, emit a change warning.
411411
*/
412-
def isLeadingInfixOperator(inConditional: Boolean = true) = (
412+
def isLeadingInfixOperator(inConditional: Boolean = true) =
413413
allowLeadingInfixOperators
414414
&& ( token == BACKQUOTED_IDENT
415415
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1)))
@@ -434,7 +434,6 @@ object Scanners {
434434
sourcePos())
435435
true
436436
}
437-
)
438437

439438
/** The indentation width of the given offset */
440439
def indentWidth(offset: Offset): IndentWidth = {
@@ -533,6 +532,7 @@ object Scanners {
533532
&& canEndStatTokens.contains(lastToken)
534533
&& canStartStatTokens.contains(token)
535534
&& !isLeadingInfixOperator()
535+
&& !(openParensTokens.contains(token) && lastWidth < nextWidth && !pastBlankLine)
536536
then
537537
insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset)
538538
skipEndMarker(nextWidth)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,12 @@ object Tokens extends TokensCommon {
221221
final val atomicExprTokens: TokenSet = literalTokens | identifierTokens | BitSet(
222222
USCORE, NULL, THIS, SUPER, TRUE, FALSE, RETURN, QUOTEID, XMLSTART)
223223

224-
final val canStartExprTokens3: TokenSet = atomicExprTokens | BitSet(
225-
LBRACE, LPAREN, LBRACKET, INDENT, QUOTE, IF, WHILE, FOR, NEW, TRY, THROW)
224+
final val openParensTokens = BitSet(LBRACE, LPAREN, LBRACKET)
225+
226+
final val canStartExprTokens3: TokenSet =
227+
atomicExprTokens
228+
| openParensTokens
229+
| BitSet(INDENT, QUOTE, IF, WHILE, FOR, NEW, TRY, THROW)
226230

227231
final val canStartExprTokens2: TokenSet = canStartExprTokens3 | BitSet(DO)
228232

tests/neg/i1707.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ object DepBug {
1616
import d._
1717
a m (b)
1818
}
19-
{ // error: Null does not take parameters (follow on)
19+
{
2020
import dep._
2121
a m (b) // error: not found: a
2222
}

tests/pos/indented-parens.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
def f(x: Int)
2+
(y: Int): Int = x + y
3+
4+
5+
@main def Test =
6+
val x = f(1)
7+
(2)
8+
+ f(1)
9+
{2}
10+
{ println(x) }
11+

tests/pos/path-from-class.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Paths rooted in outer classes. Not sure to what degree we
2+
// want to support them.
3+
class Outer {
4+
type Bar
5+
object inner {
6+
type Foo
7+
def foo: Foo = ???
8+
}
9+
def bar: Bar = ???
10+
}
11+
12+
object Main {
13+
val a = new Outer
14+
val b = new Outer
15+
def all = List(a.inner.foo, a.inner.foo)
16+
// The inferred type is [Outer#inner.Foo], but this cannot be written in source
17+
def bars: Outer # Bar = identity(a.bar)
18+
}

tests/pos/tailcall/t6891.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object O6891 {
1515
def beppy[C](c: => C) = {
1616
() => c
1717
@tailrec def loop(x: value.type): Unit = loop(x)
18-
() => c
18+
() => c
1919
()
2020
}
2121
}

tests/run/Pouring.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ class Pouring(capacity: Vector[Int]):
1818

1919
val moves =
2020
val glasses = 0 until capacity.length
21-
(for g <- glasses yield Move.Empty(g))
21+
22+
(for g <- glasses yield Move.Empty(g))
2223
++ (for g <- glasses yield Move.Fill(g))
2324
++ (for g1 <- glasses; g2 <- glasses if g1 != g2 yield Move.Pour(g1, g2))
2425

0 commit comments

Comments
 (0)