Skip to content

Commit e3d6db6

Browse files
committed
Better error recovery in comma-separated lists
1 parent afee167 commit e3d6db6

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
@@ -558,19 +558,28 @@ object Parsers {
558558
def inDefScopeBraces[T](body: => T, rewriteWithColon: Boolean = false): T =
559559
inBracesOrIndented(body, rewriteWithColon)
560560

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

572-
def commaSeparated[T](part: () => T): List[T] = tokenSeparated(COMMA, part)
573-
574583
def inSepRegion[T](f: Region => Region)(op: => T): T =
575584
val cur = in.currentRegion
576585
in.currentRegion = f(cur)
@@ -1514,7 +1523,7 @@ object Parsers {
15141523
/** FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’
15151524
*/
15161525
def funParamClause(): List[ValDef] =
1517-
inParens(commaSeparated(() => typedFunParam(in.offset, ident())))
1526+
inParens(commaSeparated(() => typedFunParam(in.offset, ident()), RPAREN))
15181527

15191528
def funParamClauses(): List[List[ValDef]] =
15201529
if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil
@@ -1627,7 +1636,7 @@ object Parsers {
16271636
else
16281637
def singletonArgs(t: Tree): Tree =
16291638
if in.token == LPAREN && in.featureEnabled(Feature.dependent)
1630-
then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton))))
1639+
then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton, RPAREN))))
16311640
else t
16321641
singletonArgs(simpleType1())
16331642

@@ -1643,7 +1652,7 @@ object Parsers {
16431652
def simpleType1() = simpleTypeRest {
16441653
if in.token == LPAREN then
16451654
atSpan(in.offset) {
1646-
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true)))
1655+
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true, RPAREN)))
16471656
}
16481657
else if in.token == LBRACE then
16491658
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
@@ -1727,7 +1736,7 @@ object Parsers {
17271736
* | NamedTypeArg {`,' NamedTypeArg}
17281737
* NamedTypeArg ::= id `=' Type
17291738
*/
1730-
def argTypes(namedOK: Boolean, wildOK: Boolean): List[Tree] = {
1739+
def argTypes(namedOK: Boolean, wildOK: Boolean, expectedEnd: Token): List[Tree] = {
17311740

17321741
def argType() = {
17331742
val t = typ()
@@ -1744,7 +1753,7 @@ object Parsers {
17441753
val rest =
17451754
if (in.token == COMMA) {
17461755
in.nextToken()
1747-
commaSeparated(arg)
1756+
commaSeparated(arg, expectedEnd)
17481757
}
17491758
else Nil
17501759
first :: rest
@@ -1757,7 +1766,7 @@ object Parsers {
17571766
case firstArg =>
17581767
otherArgs(firstArg, () => argType())
17591768
}
1760-
else commaSeparated(() => argType())
1769+
else commaSeparated(() => argType(), expectedEnd)
17611770
}
17621771

17631772
/** FunArgType ::= Type | `=>' Type
@@ -1786,7 +1795,7 @@ object Parsers {
17861795
/** TypeArgs ::= `[' Type {`,' Type} `]'
17871796
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
17881797
*/
1789-
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK))
1798+
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK, RBRACKET))
17901799

17911800
/** Refinement ::= `{' RefineStatSeq `}'
17921801
*/
@@ -2150,7 +2159,7 @@ object Parsers {
21502159
var mods1 = mods
21512160
if isErased then mods1 = addModifier(mods1)
21522161
try
2153-
commaSeparated(() => binding(mods1))
2162+
commaSeparated(() => binding(mods1), RPAREN)
21542163
finally
21552164
accept(RPAREN)
21562165
else {
@@ -2382,7 +2391,7 @@ object Parsers {
23822391
/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
23832392
*/
23842393
def exprsInParensOpt(): List[Tree] =
2385-
if (in.token == RPAREN) Nil else commaSeparated(exprInParens)
2394+
if (in.token == RPAREN) Nil else commaSeparated(exprInParens, RPAREN)
23862395

23872396
/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
23882397
* | `(' [ExprsInParens `,'] PostfixExpr `*' ')'
@@ -2392,9 +2401,9 @@ object Parsers {
23922401
(Nil, false)
23932402
else if isIdent(nme.using) then
23942403
in.nextToken()
2395-
(commaSeparated(argumentExpr), true)
2404+
(commaSeparated(argumentExpr, RPAREN), true)
23962405
else
2397-
(commaSeparated(argumentExpr), false)
2406+
(commaSeparated(argumentExpr, RPAREN), false)
23982407
}
23992408

24002409
/** ArgumentExprs ::= ParArgumentExprs
@@ -2538,7 +2547,7 @@ object Parsers {
25382547
if (leading == LBRACE || in.token == CASE)
25392548
enumerators()
25402549
else {
2541-
val pats = patternsOpt()
2550+
val pats = patternsOpt(EMPTY)
25422551
val pat =
25432552
if (in.token == RPAREN || pats.length > 1) {
25442553
wrappedEnums = false
@@ -2730,7 +2739,7 @@ object Parsers {
27302739
case USCORE =>
27312740
wildcardIdent()
27322741
case LPAREN =>
2733-
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) }
2742+
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt(RPAREN))) }
27342743
case QUOTE =>
27352744
simpleExpr(Location.InPattern)
27362745
case XMLSTART =>
@@ -2766,17 +2775,17 @@ object Parsers {
27662775

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

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

27752784
/** ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
27762785
* | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’
27772786
*/
27782787
def argumentPatterns(): List[Tree] =
2779-
inParens(patternsOpt(Location.InPatternArgs))
2788+
inParens(patternsOpt(RPAREN, Location.InPatternArgs))
27802789

27812790
/* -------- MODIFIERS and ANNOTATIONS ------------------------------------------- */
27822791

@@ -2957,7 +2966,7 @@ object Parsers {
29572966
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
29582967
}
29592968
}
2960-
commaSeparated(() => typeParam())
2969+
commaSeparated(() => typeParam(), RBRACKET)
29612970
}
29622971

29632972
def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] =
@@ -2966,7 +2975,7 @@ object Parsers {
29662975
/** ContextTypes ::= FunArgType {‘,’ FunArgType}
29672976
*/
29682977
def contextTypes(ofClass: Boolean, nparams: Int, impliedMods: Modifiers): List[ValDef] =
2969-
val tps = commaSeparated(funArgType)
2978+
val tps = commaSeparated(funArgType, RPAREN)
29702979
var counter = nparams
29712980
def nextIdx = { counter += 1; counter }
29722981
val paramFlags = if ofClass then Private | Local | ParamAccessor else Param
@@ -3070,7 +3079,7 @@ object Parsers {
30703079
!impliedMods.is(Given)
30713080
|| startParamTokens.contains(in.token)
30723081
|| isIdent && (in.name == nme.inline || in.lookahead.isColon())
3073-
if isParams then commaSeparated(() => param())
3082+
if isParams then commaSeparated(() => param(), RPAREN)
30743083
else contextTypes(ofClass, nparams, impliedMods)
30753084
checkVarArgsRules(clause)
30763085
clause
@@ -3764,7 +3773,7 @@ object Parsers {
37643773
val derived =
37653774
if (isIdent(nme.derives)) {
37663775
in.nextToken()
3767-
tokenSeparated(COMMA, () => convertToTypeId(qualId()))
3776+
commaSeparated(() => convertToTypeId(qualId()))
37683777
}
37693778
else Nil
37703779
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)