Skip to content

Commit 7407a31

Browse files
committed
Better error recovery in comma-separated lists
1 parent 3ca087c commit 7407a31

File tree

4 files changed

+89
-29
lines changed

4 files changed

+89
-29
lines changed

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

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -559,19 +559,28 @@ object Parsers {
559559
def inDefScopeBraces[T](body: => T, rewriteWithColon: Boolean = false): T =
560560
inBracesOrIndented(body, rewriteWithColon)
561561

562-
/** part { `separator` part }
563-
*/
564-
def tokenSeparated[T](separator: Int, part: () => T): List[T] = {
562+
/** part { `,` part }
563+
* @param expectedEnd If set to something other than [[EMPTY]],
564+
* assume this comma separated list must be followed by this token.
565+
* If the parser consumes a `part` that is not followed by a comma or this expected
566+
* token, issue a syntax error and try to recover at the next safe point.
567+
*/
568+
def commaSeparated[T](part: () => T, expectedEnd: Token = EMPTY): List[T] = {
565569
val ts = new ListBuffer[T] += part()
566-
while (in.token == separator) {
570+
while (in.token == COMMA) {
567571
in.nextToken()
568572
ts += part()
569573
}
574+
if (expectedEnd != EMPTY && in.token != expectedEnd) {
575+
// As a side effect, will skip to the nearest safe point, which might be a comma
576+
syntaxErrorOrIncomplete(ExpectedTokenButFound(expectedEnd, in.token))
577+
if (in.token == COMMA) {
578+
ts ++= commaSeparated(part, expectedEnd)
579+
}
580+
}
570581
ts.toList
571582
}
572583

573-
def commaSeparated[T](part: () => T): List[T] = tokenSeparated(COMMA, part)
574-
575584
def inSepRegion[T](f: Region => Region)(op: => T): T =
576585
val cur = in.currentRegion
577586
in.currentRegion = f(cur)
@@ -1515,7 +1524,7 @@ object Parsers {
15151524
/** FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’
15161525
*/
15171526
def funParamClause(): List[ValDef] =
1518-
inParens(commaSeparated(() => typedFunParam(in.offset, ident())))
1527+
inParens(commaSeparated(() => typedFunParam(in.offset, ident()), RPAREN))
15191528

15201529
def funParamClauses(): List[List[ValDef]] =
15211530
if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil
@@ -1628,7 +1637,7 @@ object Parsers {
16281637
else
16291638
def singletonArgs(t: Tree): Tree =
16301639
if in.token == LPAREN && in.featureEnabled(Feature.dependent)
1631-
then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton))))
1640+
then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton, RPAREN))))
16321641
else t
16331642
singletonArgs(simpleType1())
16341643

@@ -1644,7 +1653,7 @@ object Parsers {
16441653
def simpleType1() = simpleTypeRest {
16451654
if in.token == LPAREN then
16461655
atSpan(in.offset) {
1647-
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true)))
1656+
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true, RPAREN)))
16481657
}
16491658
else if in.token == LBRACE then
16501659
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
@@ -1728,7 +1737,7 @@ object Parsers {
17281737
* | NamedTypeArg {`,' NamedTypeArg}
17291738
* NamedTypeArg ::= id `=' Type
17301739
*/
1731-
def argTypes(namedOK: Boolean, wildOK: Boolean): List[Tree] = {
1740+
def argTypes(namedOK: Boolean, wildOK: Boolean, expectedEnd: Token): List[Tree] = {
17321741

17331742
def argType() = {
17341743
val t = typ()
@@ -1745,7 +1754,7 @@ object Parsers {
17451754
val rest =
17461755
if (in.token == COMMA) {
17471756
in.nextToken()
1748-
commaSeparated(arg)
1757+
commaSeparated(arg, expectedEnd)
17491758
}
17501759
else Nil
17511760
first :: rest
@@ -1758,7 +1767,7 @@ object Parsers {
17581767
case firstArg =>
17591768
otherArgs(firstArg, () => argType())
17601769
}
1761-
else commaSeparated(() => argType())
1770+
else commaSeparated(() => argType(), expectedEnd)
17621771
}
17631772

17641773
/** FunArgType ::= Type | `=>' Type
@@ -1787,7 +1796,7 @@ object Parsers {
17871796
/** TypeArgs ::= `[' Type {`,' Type} `]'
17881797
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
17891798
*/
1790-
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK))
1799+
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK, RBRACKET))
17911800

17921801
/** Refinement ::= `{' RefineStatSeq `}'
17931802
*/
@@ -2151,7 +2160,7 @@ object Parsers {
21512160
var mods1 = mods
21522161
if isErased then mods1 = addModifier(mods1)
21532162
try
2154-
commaSeparated(() => binding(mods1))
2163+
commaSeparated(() => binding(mods1), RPAREN)
21552164
finally
21562165
accept(RPAREN)
21572166
else {
@@ -2383,7 +2392,7 @@ object Parsers {
23832392
/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
23842393
*/
23852394
def exprsInParensOpt(): List[Tree] =
2386-
if (in.token == RPAREN) Nil else commaSeparated(exprInParens)
2395+
if (in.token == RPAREN) Nil else commaSeparated(exprInParens, RPAREN)
23872396

23882397
/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
23892398
* | `(' [ExprsInParens `,'] PostfixExpr `*' ')'
@@ -2393,9 +2402,9 @@ object Parsers {
23932402
(Nil, false)
23942403
else if isIdent(nme.using) then
23952404
in.nextToken()
2396-
(commaSeparated(argumentExpr), true)
2405+
(commaSeparated(argumentExpr, RPAREN), true)
23972406
else
2398-
(commaSeparated(argumentExpr), false)
2407+
(commaSeparated(argumentExpr, RPAREN), false)
23992408
}
24002409

24012410
/** ArgumentExprs ::= ParArgumentExprs
@@ -2539,7 +2548,7 @@ object Parsers {
25392548
if (leading == LBRACE || in.token == CASE)
25402549
enumerators()
25412550
else {
2542-
val pats = patternsOpt()
2551+
val pats = patternsOpt(EMPTY)
25432552
val pat =
25442553
if (in.token == RPAREN || pats.length > 1) {
25452554
wrappedEnums = false
@@ -2731,7 +2740,7 @@ object Parsers {
27312740
case USCORE =>
27322741
wildcardIdent()
27332742
case LPAREN =>
2734-
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) }
2743+
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt(RPAREN))) }
27352744
case QUOTE =>
27362745
simpleExpr(Location.InPattern)
27372746
case XMLSTART =>
@@ -2767,17 +2776,17 @@ object Parsers {
27672776

27682777
/** Patterns ::= Pattern [`,' Pattern]
27692778
*/
2770-
def patterns(location: Location = Location.InPattern): List[Tree] =
2771-
commaSeparated(() => pattern(location))
2779+
def patterns(expectedEnd: Token = EMPTY, location: Location = Location.InPattern): List[Tree] =
2780+
commaSeparated(() => pattern(location), expectedEnd)
27722781

2773-
def patternsOpt(location: Location = Location.InPattern): List[Tree] =
2774-
if (in.token == RPAREN) Nil else patterns(location)
2782+
def patternsOpt(expectedEnd: Token, location: Location = Location.InPattern): List[Tree] =
2783+
if (in.token == RPAREN) Nil else patterns(expectedEnd, location)
27752784

27762785
/** ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
27772786
* | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’
27782787
*/
27792788
def argumentPatterns(): List[Tree] =
2780-
inParens(patternsOpt(Location.InPatternArgs))
2789+
inParens(patternsOpt(RPAREN, Location.InPatternArgs))
27812790

27822791
/* -------- MODIFIERS and ANNOTATIONS ------------------------------------------- */
27832792

@@ -2958,7 +2967,7 @@ object Parsers {
29582967
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
29592968
}
29602969
}
2961-
commaSeparated(() => typeParam())
2970+
commaSeparated(() => typeParam(), RBRACKET)
29622971
}
29632972

29642973
def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] =
@@ -2967,7 +2976,7 @@ object Parsers {
29672976
/** ContextTypes ::= FunArgType {‘,’ FunArgType}
29682977
*/
29692978
def contextTypes(ofClass: Boolean, nparams: Int, impliedMods: Modifiers): List[ValDef] =
2970-
val tps = commaSeparated(funArgType)
2979+
val tps = commaSeparated(funArgType, RPAREN)
29712980
var counter = nparams
29722981
def nextIdx = { counter += 1; counter }
29732982
val paramFlags = if ofClass then Private | Local | ParamAccessor else Param
@@ -3071,7 +3080,7 @@ object Parsers {
30713080
!impliedMods.is(Given)
30723081
|| startParamTokens.contains(in.token)
30733082
|| isIdent && (in.name == nme.inline || in.lookahead.isColon())
3074-
if isParams then commaSeparated(() => param())
3083+
if isParams then commaSeparated(() => param(), RPAREN)
30753084
else contextTypes(ofClass, nparams, impliedMods)
30763085
checkVarArgsRules(clause)
30773086
clause
@@ -3765,7 +3774,7 @@ object Parsers {
37653774
val derived =
37663775
if (isIdent(nme.derives)) {
37673776
in.nextToken()
3768-
tokenSeparated(COMMA, () => convertToTypeId(qualId()))
3777+
commaSeparated(() => convertToTypeId(qualId()))
37693778
}
37703779
else Nil
37713780
possibleTemplateStart()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:21 ----------------------------------------------------
2+
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
3+
| ^
4+
| ')' expected, but integer literal found
5+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:26 ----------------------------------------------------
6+
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
7+
| ^^^
8+
| ':' expected, but identifier found
9+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:42 ----------------------------------------------------
10+
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
11+
| ^
12+
| ')' expected, but integer literal found
13+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:47 ----------------------------------------------------
14+
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
15+
| ^
16+
| ':' expected, but '=' found
17+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:11:16 ---------------------------------------------------
18+
11 | case Plus(4 1) => // error
19+
| ^
20+
| ')' expected, but integer literal found
21+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:12:16 ---------------------------------------------------
22+
12 | case Plus(4 5 6 7, 1, 2 3) => // error // error
23+
| ^
24+
| ')' expected, but integer literal found
25+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:12:28 ---------------------------------------------------
26+
12 | case Plus(4 5 6 7, 1, 2 3) => // error // error
27+
| ^
28+
| ')' expected, but integer literal found
29+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:14:12 ---------------------------------------------------
30+
14 | val x: A[T=Int, T=Int] = ??? // error // error
31+
| ^
32+
| ']' expected, but '=' found
33+
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:14:19 ---------------------------------------------------
34+
14 | val x: A[T=Int, T=Int] = ??? // error // error
35+
| ^
36+
| ']' expected, but '=' found
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class A[T]
2+
object o {
3+
def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
4+
5+
case class Plus(a: Int, b: Int)
6+
7+
object Plus {
8+
def unapply(r: Int): Plus = Plus(r - 1, 1)
9+
}
10+
5 match {
11+
case Plus(4 1) => // error
12+
case Plus(4 5 6 7, 1, 2 3) => // error // error
13+
}
14+
val x: A[T=Int, T=Int] = ??? // error // error
15+
}

tests/neg/i1679.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class A[T]
22
object o {
33
// Testing compiler crash, this test should be modified when named type argument are completely implemented
4-
val x: A[T=Int, T=Int] = ??? // error: ']' expected, but '=' found // error
4+
val x: A[T=Int, T=Int] = ??? // error: ']' expected, but '=' found // error: ']' expected, but '=' found
55
}

0 commit comments

Comments
 (0)