Skip to content

Commit 7206663

Browse files
authored
Merge pull request #8707 from dotty-staging/change-newlines
Use indentation to control newline insertion before parentheses
2 parents 38cb5e3 + 5a87090 commit 7206663

File tree

16 files changed

+123
-21
lines changed

16 files changed

+123
-21
lines changed

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

+2-3
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

+15-8
Original file line numberDiff line numberDiff line change
@@ -1276,10 +1276,17 @@ object Parsers {
12761276
if (in.token == COLONEOL) in.nextToken()
12771277
}
12781278

1279-
def possibleBracesStart(): Unit = {
1279+
def argumentStart(): Unit =
12801280
colonAtEOLOpt()
1281-
newLineOptWhenFollowedBy(LBRACE)
1282-
}
1281+
if migrateTo3 && in.token == NEWLINE && in.next.token == LBRACE then
1282+
in.nextToken()
1283+
if in.indentWidth(in.offset) == in.currentRegion.indentWidth then
1284+
ctx.errorOrMigrationWarning(
1285+
i"""This opening brace will start a new statement in Scala 3.
1286+
|It needs to be indented to the right to keep being treated as
1287+
|an argument to the previous expression.${rewriteNotice()}""",
1288+
in.sourcePos())
1289+
patch(source, Span(in.offset), " ")
12831290

12841291
def possibleTemplateStart(isNew: Boolean = false): Unit =
12851292
in.observeColonEOL()
@@ -1509,7 +1516,7 @@ object Parsers {
15091516
val refinedType: () => Tree = () => refinedTypeRest(withType())
15101517

15111518
def refinedTypeRest(t: Tree): Tree = {
1512-
possibleBracesStart()
1519+
argumentStart()
15131520
if (in.isNestedStart)
15141521
refinedTypeRest(atSpan(startOffset(t)) { RefinedTypeTree(rejectWildcardType(t), refinement()) })
15151522
else t
@@ -2265,7 +2272,7 @@ object Parsers {
22652272
}
22662273

22672274
def simpleExprRest(t: Tree, canApply: Boolean = true): Tree = {
2268-
if (canApply) possibleBracesStart()
2275+
if (canApply) argumentStart()
22692276
in.token match {
22702277
case DOT =>
22712278
in.nextToken()
@@ -2338,7 +2345,7 @@ object Parsers {
23382345
/** ArgumentExprss ::= {ArgumentExprs}
23392346
*/
23402347
def argumentExprss(fn: Tree): Tree = {
2341-
possibleBracesStart()
2348+
argumentStart()
23422349
if (in.token == LPAREN || in.isNestedStart) argumentExprss(mkApply(fn, argumentExprs()))
23432350
else fn
23442351
}
@@ -2831,7 +2838,7 @@ object Parsers {
28312838
}
28322839

28332840
def annotations(skipNewLines: Boolean = false): List[Tree] = {
2834-
if (skipNewLines) newLineOptWhenFollowedBy(AT)
2841+
if (skipNewLines) newLinesOptWhenFollowedBy(AT)
28352842
if (in.token == AT) annot() :: annotations(skipNewLines)
28362843
else Nil
28372844
}
@@ -3358,7 +3365,7 @@ object Parsers {
33583365
*/
33593366
def selfInvocation(): Tree =
33603367
atSpan(accept(THIS)) {
3361-
possibleBracesStart()
3368+
argumentStart()
33623369
argumentExprss(mkApply(Ident(nme.CONSTRUCTOR), argumentExprs()))
33633370
}
33643371

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ object Scanners {
360360
* and a token that can start an expression.
361361
* If a leading infix operator is found and the source version is `3.0-migration`, emit a change warning.
362362
*/
363-
def isLeadingInfixOperator(inConditional: Boolean = true) = (
363+
def isLeadingInfixOperator(inConditional: Boolean = true) =
364364
allowLeadingInfixOperators
365365
&& ( token == BACKQUOTED_IDENT
366366
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1)))
@@ -385,7 +385,12 @@ object Scanners {
385385
sourcePos())
386386
true
387387
}
388-
)
388+
389+
def isContinuingParens() =
390+
openParensTokens.contains(token)
391+
&& !pastBlankLine
392+
&& !migrateTo3
393+
&& !noindentSyntax
389394

390395
/** The indentation width of the given offset */
391396
def indentWidth(offset: Offset): IndentWidth = {
@@ -484,6 +489,7 @@ object Scanners {
484489
&& canEndStatTokens.contains(lastToken)
485490
&& canStartStatTokens.contains(token)
486491
&& !isLeadingInfixOperator()
492+
&& !(lastWidth < nextWidth && isContinuingParens())
487493
then
488494
insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset)
489495
else if indentIsSignificant then

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

+6-2
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

compiler/test/dotty/tools/dotc/CompilationTests.scala

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class CompilationTests extends ParallelTesting {
129129
compileFile("tests/neg-custom-args/i3246.scala", scala2CompatMode),
130130
compileFile("tests/neg-custom-args/overrideClass.scala", scala2CompatMode),
131131
compileFile("tests/neg-custom-args/ovlazy.scala", scala2CompatMode.and("-migration", "-Xfatal-warnings")),
132+
compileFile("tests/neg-custom-args/newline-braces.scala", scala2CompatMode.and("-migration", "-Xfatal-warnings")),
132133
compileFile("tests/neg-custom-args/autoTuplingTest.scala", defaultOptions.andLanguageFeature("noAutoTupling")),
133134
compileFile("tests/neg-custom-args/nopredef.scala", defaultOptions.and("-Yno-predef")),
134135
compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")),

docs/docs/internals/syntax.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,12 @@ ExprInParens ::= PostfixExpr ‘:’ Type
233233
ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ exprs
234234
| ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ‘)’ exprs :+ Typed(expr, Ident(wildcardStar))
235235
ArgumentExprs ::= ParArgumentExprs
236-
| [nl] BlockExpr
236+
| BlockExpr
237237
BlockExpr ::= ‘{’ (CaseClauses | Block) ‘}’
238238
Block ::= {BlockStat semi} [BlockResult] Block(stats, expr?)
239239
BlockStat ::= Import
240-
| {Annotation [nl]} [‘implicit’ | ‘lazy’] Def
241-
| {Annotation [nl]} {LocalModifier} TmplDef
240+
| {Annotation {nl}} [‘implicit’ | ‘lazy’] Def
241+
| {Annotation {nl}} {LocalModifier} TmplDef
242242
| Expr1
243243
| EndMarker
244244
@@ -362,7 +362,7 @@ ValDcl ::= ids ‘:’ Type
362362
VarDcl ::= ids ‘:’ Type PatDef(_, ids, tpe, EmptyTree)
363363
DefDcl ::= DefSig ‘:’ Type DefDef(_, name, tparams, vparamss, tpe, EmptyTree)
364364
DefSig ::= id [DefTypeParamClause] DefParamClauses
365-
| ExtParamClause [nl] [‘.’] id DefParamClauses
365+
| ExtParamClause {nl} [‘.’] id DefParamClauses
366366
TypeDcl ::= id [TypeParamClause] SubtypeBounds [‘=’ Type] TypeDefTree(_, name, tparams, bound
367367
368368
Def ::= ‘val’ PatDef
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Error: tests/neg-custom-args/newline-braces.scala:3:2 ---------------------------------------------------------------
2+
3 | { x => // error (migration)
3+
| ^
4+
| This opening brace will start a new statement in Scala 3.
5+
| It needs to be indented to the right to keep being treated as
6+
| an argument to the previous expression.
7+
| This construct can be rewritten automatically under -rewrite.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def f: List[Int] = {
2+
List(1, 2, 3).map // no newline inserted here in Scala-2 compat mode
3+
{ x => // error (migration)
4+
x + 1
5+
}
6+
}

tests/neg/i1707.scala

+1-1
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
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
val (x, y) =
2+
try {
3+
???
4+
(1, 2)
5+
}
6+
catch {
7+
case e if !true =>
8+
val x = 2
9+
val foo = e match {
10+
case e: java.io.IOException => ???
11+
}
12+
(1, 2)
13+
}

tests/pos-scala2/newline-braces.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def f: List[Int] = {
2+
List(1, 2, 3).map // no newline inserted here in Scala-2 compat mode
3+
{ x =>
4+
x + 1
5+
}
6+
}

tests/pos/indented-parens.scala

+11
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/newline-braces.scala

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Foo
2+
{
3+
val x: Int = 5
4+
}
5+
6+
def bar(): Int =
7+
{
8+
val x = ???
9+
x
10+
}
11+
12+
def f: Int => Int =
13+
List(1, 2, 3).map // newline inserted here
14+
{ (x: Int) =>
15+
x + 1
16+
}
17+
18+
val x =
19+
true && // newline inserted here but skipped because of trailing &&
20+
{
21+
val xyz = true
22+
xyz && false
23+
}

tests/pos/path-from-class.scala

+18
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

+1-1
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

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Pouring(capacity: Vector[Int]):
1818

1919
val moves =
2020
val glasses = 0 until capacity.length
21+
2122
(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))

0 commit comments

Comments
 (0)