Skip to content

Commit

Permalink
Better error recovery in comma-separated lists
Browse files Browse the repository at this point in the history
  • Loading branch information
adampauls committed Feb 18, 2022
1 parent 24fdf53 commit 5e9d681
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 29 deletions.
64 changes: 36 additions & 28 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -553,19 +553,27 @@ object Parsers {
def inDefScopeBraces[T](body: => T, rewriteWithColon: Boolean = false): T =
inBracesOrIndented(body, rewriteWithColon)

/** part { `separator` part }
*/
def tokenSeparated[T](separator: Int, part: () => T): List[T] = {
/** part { `,` part }
* @param expectedEnd If set to something other than [[EMPTY]],
* assume this comma separated list must be followed by this token.
* If the parser consumes a `part` that is not followed by a comma or this expected
* token, issue a syntax error and try to recover at the next safe point.
*/
def commaSeparated[T](part: () => T, expectedEnd: Token = EMPTY): List[T] = {
val ts = new ListBuffer[T] += part()
while (in.token == separator) {
while (in.token == COMMA) {
in.nextToken()
ts += part()
}
if (expectedEnd != EMPTY && in.token != expectedEnd) {
syntaxErrorOrIncomplete(ExpectedTokenButFound(expectedEnd, in.token))
if (in.token == COMMA) {
ts ++= commaSeparated(part, expectedEnd)
}
}
ts.toList
}

def commaSeparated[T](part: () => T): List[T] = tokenSeparated(COMMA, part)

def inSepRegion[T](f: Region => Region)(op: => T): T =
val cur = in.currentRegion
in.currentRegion = f(cur)
Expand Down Expand Up @@ -1509,7 +1517,7 @@ object Parsers {
/** FunParamClause ::= ‘(’ TypedFunParam {‘,’ TypedFunParam } ‘)’
*/
def funParamClause(): List[ValDef] =
inParens(commaSeparated(() => typedFunParam(in.offset, ident())))
inParens(commaSeparated(() => typedFunParam(in.offset, ident()), RPAREN))

def funParamClauses(): List[List[ValDef]] =
if in.token == LPAREN then funParamClause() :: funParamClauses() else Nil
Expand Down Expand Up @@ -1622,7 +1630,7 @@ object Parsers {
else
def singletonArgs(t: Tree): Tree =
if in.token == LPAREN && in.featureEnabled(Feature.dependent)
then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton))))
then singletonArgs(AppliedTypeTree(t, inParens(commaSeparated(singleton, RPAREN))))
else t
singletonArgs(simpleType1())

Expand All @@ -1638,7 +1646,7 @@ object Parsers {
def simpleType1() = simpleTypeRest {
if in.token == LPAREN then
atSpan(in.offset) {
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true)))
makeTupleOrParens(inParens(argTypes(namedOK = false, wildOK = true, RPAREN)))
}
else if in.token == LBRACE then
atSpan(in.offset) { RefinedTypeTree(EmptyTree, refinement(indentOK = false)) }
Expand Down Expand Up @@ -1722,7 +1730,7 @@ object Parsers {
* | NamedTypeArg {`,' NamedTypeArg}
* NamedTypeArg ::= id `=' Type
*/
def argTypes(namedOK: Boolean, wildOK: Boolean): List[Tree] = {
def argTypes(namedOK: Boolean, wildOK: Boolean, expectedEnd: Token): List[Tree] = {

def argType() = {
val t = typ()
Expand All @@ -1739,7 +1747,7 @@ object Parsers {
val rest =
if (in.token == COMMA) {
in.nextToken()
commaSeparated(arg)
commaSeparated(arg, expectedEnd)
}
else Nil
first :: rest
Expand All @@ -1752,7 +1760,7 @@ object Parsers {
case firstArg =>
otherArgs(firstArg, () => argType())
}
else commaSeparated(() => argType())
else commaSeparated(() => argType(), expectedEnd)
}

/** FunArgType ::= Type | `=>' Type
Expand Down Expand Up @@ -1781,7 +1789,7 @@ object Parsers {
/** TypeArgs ::= `[' Type {`,' Type} `]'
* NamedTypeArgs ::= `[' NamedTypeArg {`,' NamedTypeArg} `]'
*/
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK))
def typeArgs(namedOK: Boolean, wildOK: Boolean): List[Tree] = inBrackets(argTypes(namedOK, wildOK, RBRACKET))

/** Refinement ::= `{' RefineStatSeq `}'
*/
Expand Down Expand Up @@ -2145,7 +2153,7 @@ object Parsers {
var mods1 = mods
if isErased then mods1 = addModifier(mods1)
try
commaSeparated(() => binding(mods1))
commaSeparated(() => binding(mods1), RPAREN)
finally
accept(RPAREN)
else {
Expand Down Expand Up @@ -2376,7 +2384,7 @@ object Parsers {
/** ExprsInParens ::= ExprInParens {`,' ExprInParens}
*/
def exprsInParensOpt(): List[Tree] =
if (in.token == RPAREN) Nil else commaSeparated(exprInParens)
if (in.token == RPAREN) Nil else commaSeparated(exprInParens, RPAREN)

/** ParArgumentExprs ::= `(' [‘using’] [ExprsInParens] `)'
* | `(' [ExprsInParens `,'] PostfixExpr `*' ')'
Expand All @@ -2386,9 +2394,9 @@ object Parsers {
(Nil, false)
else if isIdent(nme.using) then
in.nextToken()
(commaSeparated(argumentExpr), true)
(commaSeparated(argumentExpr, RPAREN), true)
else
(commaSeparated(argumentExpr), false)
(commaSeparated(argumentExpr, RPAREN), false)
}

/** ArgumentExprs ::= ParArgumentExprs
Expand Down Expand Up @@ -2532,7 +2540,7 @@ object Parsers {
if (leading == LBRACE || in.token == CASE)
enumerators()
else {
val pats = patternsOpt()
val pats = patternsOpt(EMPTY)
val pat =
if (in.token == RPAREN || pats.length > 1) {
wrappedEnums = false
Expand Down Expand Up @@ -2724,7 +2732,7 @@ object Parsers {
case USCORE =>
wildcardIdent()
case LPAREN =>
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt())) }
atSpan(in.offset) { makeTupleOrParens(inParens(patternsOpt(RPAREN))) }
case QUOTE =>
simpleExpr(Location.InPattern)
case XMLSTART =>
Expand Down Expand Up @@ -2759,17 +2767,17 @@ object Parsers {

/** Patterns ::= Pattern [`,' Pattern]
*/
def patterns(location: Location = Location.InPattern): List[Tree] =
commaSeparated(() => pattern(location))
def patterns(expectedEnd: Token = EMPTY, location: Location = Location.InPattern): List[Tree] =
commaSeparated(() => pattern(location), expectedEnd)

def patternsOpt(location: Location = Location.InPattern): List[Tree] =
if (in.token == RPAREN) Nil else patterns(location)
def patternsOpt(expectedEnd: Token, location: Location = Location.InPattern): List[Tree] =
if (in.token == RPAREN) Nil else patterns(expectedEnd, location)

/** ArgumentPatterns ::= ‘(’ [Patterns] ‘)’
* | ‘(’ [Patterns ‘,’] PatVar ‘*’ ‘)’
*/
def argumentPatterns(): List[Tree] =
inParens(patternsOpt(Location.InPatternArgs))
inParens(patternsOpt(RPAREN, Location.InPatternArgs))

/* -------- MODIFIERS and ANNOTATIONS ------------------------------------------- */

Expand Down Expand Up @@ -2950,7 +2958,7 @@ object Parsers {
TypeDef(name, lambdaAbstract(hkparams, bounds)).withMods(mods)
}
}
commaSeparated(() => typeParam())
commaSeparated(() => typeParam(), RBRACKET)
}

def typeParamClauseOpt(ownerKind: ParamOwner.Value): List[TypeDef] =
Expand All @@ -2959,7 +2967,7 @@ object Parsers {
/** ContextTypes ::= FunArgType {‘,’ FunArgType}
*/
def contextTypes(ofClass: Boolean, nparams: Int, impliedMods: Modifiers): List[ValDef] =
val tps = commaSeparated(funArgType)
val tps = commaSeparated(funArgType, RPAREN)
var counter = nparams
def nextIdx = { counter += 1; counter }
val paramFlags = if ofClass then Private | Local | ParamAccessor else Param
Expand Down Expand Up @@ -3063,7 +3071,7 @@ object Parsers {
!impliedMods.is(Given)
|| startParamTokens.contains(in.token)
|| isIdent && (in.name == nme.inline || in.lookahead.isColon())
if isParams then commaSeparated(() => param())
if isParams then commaSeparated(() => param(), RPAREN)
else contextTypes(ofClass, nparams, impliedMods)
checkVarArgsRules(clause)
clause
Expand Down Expand Up @@ -3755,7 +3763,7 @@ object Parsers {
val derived =
if (isIdent(nme.derives)) {
in.nextToken()
tokenSeparated(COMMA, () => convertToTypeId(qualId()))
commaSeparated(() => convertToTypeId(qualId()))
}
else Nil
possibleTemplateStart()
Expand Down
36 changes: 36 additions & 0 deletions tests/neg/comma-separated-errors.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:21 ----------------------------------------------------
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
| ^
| ')' expected, but integer literal found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:26 ----------------------------------------------------
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
| ^^^
| ':' expected, but identifier found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:42 ----------------------------------------------------
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
| ^
| ')' expected, but integer literal found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:3:47 ----------------------------------------------------
3 | def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error
| ^
| ':' expected, but '=' found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:11:16 ---------------------------------------------------
11 | case Plus(4 1) => // error
| ^
| ')' expected, but integer literal found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:12:16 ---------------------------------------------------
12 | case Plus(4 5 6 7, 1, 2 3) => // error // error
| ^
| ')' expected, but integer literal found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:12:28 ---------------------------------------------------
12 | case Plus(4 5 6 7, 1, 2 3) => // error // error
| ^
| ')' expected, but integer literal found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:14:12 ---------------------------------------------------
14 | val x: A[T=Int, T=Int] = ??? // error // error
| ^
| ']' expected, but '=' found
-- [E040] Syntax Error: tests/neg/comma-separated-errors.scala:14:19 ---------------------------------------------------
14 | val x: A[T=Int, T=Int] = ??? // error // error
| ^
| ']' expected, but '=' found
15 changes: 15 additions & 0 deletions tests/neg/comma-separated-errors.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class A[T]
object o {
def foo(x: Int = 5 6, y Int = 7, z: Int 5, x = 5): Unit = () // error // error // error // error

case class Plus(a: Int, b: Int)

object Plus {
def unapply(r: Int): Plus = Plus(r - 1, 1)
}
5 match {
case Plus(4 1) => // error
case Plus(4 5 6 7, 1, 2 3) => // error // error
}
val x: A[T=Int, T=Int] = ??? // error // error
}
2 changes: 1 addition & 1 deletion tests/neg/i1679.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class A[T]
object o {
// Testing compiler crash, this test should be modified when named type argument are completely implemented
val x: A[T=Int, T=Int] = ??? // error: ']' expected, but '=' found // error
val x: A[T=Int, T=Int] = ??? // error: ']' expected, but '=' found // error: ']' expected, but '=' found
}

0 comments on commit 5e9d681

Please sign in to comment.