From b0a3572d7050addda2bfd1ef6841f92d33471944 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 15:04:48 +0200 Subject: [PATCH 1/8] Fix #2426: Use Scala-2 syntax for annotations of primary class constructors Use Scala-2 compatible syntax for annotations of primary class constructors. In fact, we can drop Scala 2's restriction that such annotations may only have one parameter list. --- .../dotty/tools/dotc/parsing/Parsers.scala | 130 +++++++++++++++--- .../dotty/tools/dotc/parsing/Scanners.scala | 36 ++++- docs/docs/internals/syntax.md | 3 +- tests/pos/i2426.scala | 13 ++ 4 files changed, 159 insertions(+), 23 deletions(-) create mode 100644 tests/pos/i2426.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d207af862b43..0e0576a0de43 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -294,6 +294,41 @@ object Parsers { } finally inFunReturnType = saved } + /** A placeholder for dummy arguments that should be re-parsed as parameters */ + val ParamNotArg = EmptyTree + + /** A flag indicating we are parsing in the annotations of a primary + * class constructor + */ + private var inClassConstrAnnots = false + + private def fromWithinClassConstr[T](body: => T): T = { + val saved = inClassConstrAnnots + try { + inClassConstrAnnots = true + body + } finally { + inClassConstrAnnots = saved + if (lookaheadTokens.nonEmpty) { + in.insertTokens(lookaheadTokens.toList) + lookaheadTokens.clear() + } + } + } + + /** Lookahead tokens for the case of annotations in class constructors. + * We store tokens in lookahead as long as they can form a valid prefix + * of a class parameter clause. + */ + private var lookaheadTokens = new ListBuffer[TokenData] + + /** Copy current token to end of lookahead */ + private def saveLookahead() = { + val lookahead = new TokenData{} + lookahead.copyFrom(in) + lookaheadTokens += lookahead + } + def migrationWarningOrError(msg: String, offset: Int = in.offset) = if (in.isScala2Mode) ctx.migrationWarning(msg, source atPos Position(offset)) @@ -1280,10 +1315,43 @@ object Parsers { if (in.token == RPAREN) Nil else commaSeparated(exprInParens) /** ParArgumentExprs ::= `(' [ExprsInParens] `)' - * | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')' \ - */ - def parArgumentExprs(): List[Tree] = - inParens(if (in.token == RPAREN) Nil else commaSeparated(argumentExpr)) + * | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')' + * + * Special treatment for arguments of primary class constructor + * annotations. Empty argument lists `(` `)` get represented + * as `List(ParamNotArg)` instead of `Nil`, indicating that the + * token sequence should be interpreted as an empty parameter clause + * instead. `ParamNotArg` can also be produced when parsing the first + * argument (see `classConstrAnnotExpr`). + * + * The method affects `lookaheadTokens` as a side effect. + * If the argument list parses as `List(ParamNotArg)`, `lookaheadTokens` + * contains the tokens that need to be replayed to parse the parameter clause. + * Otherwise, `lookaheadTokens` is empty. + */ + def parArgumentExprs(): List[Tree] = { + if (inClassConstrAnnots) { + assert(lookaheadTokens.isEmpty) + saveLookahead() + accept(LPAREN) + val args = + if (in.token == RPAREN) ParamNotArg :: Nil + else { + openParens.change(LPAREN, +1) + try commaSeparated(argumentExpr) + finally openParens.change(LPAREN, -1) + } + if (args == ParamNotArg :: Nil) in.adjustSepRegions(RPAREN) // simulate `)` without requiring it + else { + lookaheadTokens.clear() + accept(RPAREN) + } + args + } + else + inParens(if (in.token == RPAREN) Nil else commaSeparated(argumentExpr)) + + } /** ArgumentExprs ::= ParArgumentExprs * | [nl] BlockExpr @@ -1291,9 +1359,33 @@ object Parsers { def argumentExprs(): List[Tree] = if (in.token == LBRACE) blockExpr() :: Nil else parArgumentExprs() - val argumentExpr = () => exprInParens() match { - case a @ Assign(Ident(id), rhs) => cpy.NamedArg(a)(id, rhs) - case e => e + val argumentExpr = () => { + val arg = + if (inClassConstrAnnots && lookaheadTokens.nonEmpty) classConstrAnnotExpr() + else exprInParens() + arg match { + case arg @ Assign(Ident(id), rhs) => cpy.NamedArg(arg)(id, rhs) + case arg => arg + } + } + + /** Handle first argument of an argument list to an annotation of + * a primary class constructor. If the current token either cannot + * start an expression or is an identifier and is followed by `:`, + * stop parsing the rest of the expression and return `EmptyTree`, + * indicating that we should re-parse the expression as a parameter clause. + * Otherwise clear the lookahead buffer and parse as normal. + */ + def classConstrAnnotExpr() = { + saveLookahead() + if (in.token == IDENTIFIER) { + postfixExpr() match { + case Ident(_) if in.token == COLON => ParamNotArg + case t => expr1Rest(t, Location.InParens) + } + } + else if (isExprIntro) exprInParens() + else ParamNotArg } /** ArgumentExprss ::= {ArgumentExprs} @@ -1305,9 +1397,17 @@ object Parsers { } /** ParArgumentExprss ::= {ParArgumentExprs} + * + * Special treatment for arguments of primary class constructor + * annotations. If an argument list returns `List(ParamNotArg)` + * ignore it, and return prefix parsed before that list instead. */ def parArgumentExprss(fn: Tree): Tree = - if (in.token == LPAREN) parArgumentExprss(Apply(fn, parArgumentExprs())) + if (in.token == LPAREN) { + val first = parArgumentExprs() + if (inClassConstrAnnots && first == ParamNotArg :: Nil) fn + else parArgumentExprss(Apply(fn, first)) + } else fn /** BlockExpr ::= `{' (CaseClauses | Block) `}' @@ -2094,21 +2194,15 @@ object Parsers { */ def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { val tparams = typeParamClauseOpt(ParamOwner.Class) - val cmods = constrModsOpt(owner) + val cmods = fromWithinClassConstr(constrModsOpt(owner)) val vparamss = paramClauses(owner, isCaseClass) makeConstructor(tparams, vparamss).withMods(cmods) } - /** ConstrMods ::= AccessModifier - * | Annotation {Annotation} (AccessModifier | `this') + /** ConstrMods ::= {Annotation} [AccessModifier] */ - def constrModsOpt(owner: Name): Modifiers = { - val mods = modifiers(accessModifierTokens, annotsAsMods()) - if (mods.hasAnnotations && !mods.hasFlags) - if (in.token == THIS) in.nextToken() - else syntaxError(AnnotatedPrimaryConstructorRequiresModifierOrThis(owner), mods.annotations.last.pos) - mods - } + def constrModsOpt(owner: Name): Modifiers = + modifiers(accessModifierTokens, annotsAsMods()) /** ObjectDef ::= id TemplateOpt */ diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index b37d5e774c4c..53541c3d8d0f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -65,7 +65,6 @@ object Scanners { */ var errOffset: Offset = NoOffset - /** Generate an error at the given offset */ def error(msg: String, off: Offset = offset) = { ctx.error(msg, source atPos Position(off)) @@ -217,11 +216,42 @@ object Scanners { private class TokenData0 extends TokenData - /** we need one token lookahead and one token history + /** The scanner itself needs one token lookahead and one token history */ val next : TokenData = new TokenData0 private val prev : TokenData = new TokenData0 + /** The parser can also add more lookahead tokens via `insertTokens`. + * Tokens beyond `next` are stored in `following`. + */ + private var following: List[TokenData] = Nil + + /** Push a copy of token data `td` to `following` */ + private def pushCopy(td: TokenData) = { + val copy = new TokenData0 + copy.copyFrom(td) + following = copy :: following + } + + /** If following is empty, invalidate token data `td` by setting + * `td.token` to `EMPTY`. Otherwise pop head of `following` into `td`. + */ + private def popCopy(td: TokenData) = { + if (following.isEmpty) td.token = EMPTY + else { + td.copyFrom(following.head) + following = following.tail + } + + /** Insert tokens `tds` in front of current token */ + def insertTokens(tds: List[TokenData]) = { + if (next.token != EMPTY) pushCopy(next) + pushCopy(this) + following = tds ++ following + popCopy(this) + if (following.nonEmpty) popCopy(next) + } + /** a stack of tokens which indicates whether line-ends can be statement separators * also used for keeping track of nesting levels. * We keep track of the closing symbol of a region. This can be @@ -310,7 +340,7 @@ object Scanners { if (token == ERROR) adjustSepRegions(STRINGLIT) } else { this copyFrom next - next.token = EMPTY + popCopy(next) } /** Insert NEWLINE or NEWLINES if diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index e4285e20f902..3ee64121fbfd 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -335,8 +335,7 @@ TmplDef ::= ([‘case’ | `enum'] ‘class’ | trait’) ClassDef | `enum' EnumDef ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat -ConstrMods ::= AccessModifier - | Annotation {Annotation} (AccessModifier | ‘this’) +ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumDef(mods, name, tparams, template) [nl] ‘{’ EnumCaseStat {semi EnumCaseStat} ‘}’ diff --git a/tests/pos/i2426.scala b/tests/pos/i2426.scala new file mode 100644 index 000000000000..9ab22267d47f --- /dev/null +++ b/tests/pos/i2426.scala @@ -0,0 +1,13 @@ +class Foo @deprecated("foo", "2.11.0") (x: Int) + +class Bar @deprecated(x: Int) + +class Baz @deprecated() + +class C +object obj extends C + +class ann(x: C)(y: C, s: String) extends scala.annotation.Annotation + +class Bam @ann(obj)(obj, "h")(n: String) + From 38afba0e9861688f2813b5b107dd831b0da78417 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 15:10:22 +0200 Subject: [PATCH 2/8] Docs fix --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 0e0576a0de43..abd957573b8d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1341,7 +1341,8 @@ object Parsers { try commaSeparated(argumentExpr) finally openParens.change(LPAREN, -1) } - if (args == ParamNotArg :: Nil) in.adjustSepRegions(RPAREN) // simulate `)` without requiring it + if (args == ParamNotArg :: Nil) + in.adjustSepRegions(RPAREN) // simulate `)` without requiring it else { lookaheadTokens.clear() accept(RPAREN) @@ -1374,7 +1375,7 @@ object Parsers { * start an expression or is an identifier and is followed by `:`, * stop parsing the rest of the expression and return `EmptyTree`, * indicating that we should re-parse the expression as a parameter clause. - * Otherwise clear the lookahead buffer and parse as normal. + * Otherwise parse as normal. */ def classConstrAnnotExpr() = { saveLookahead() From 7dba7cab4fa16f047d82f86d297f2a728a071d1d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 15:29:35 +0200 Subject: [PATCH 3/8] Fix syntax error --- compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 53541c3d8d0f..d146816fc0b7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -233,10 +233,10 @@ object Scanners { following = copy :: following } - /** If following is empty, invalidate token data `td` by setting + /** If following is empty, invalidate token data `td` by setting * `td.token` to `EMPTY`. Otherwise pop head of `following` into `td`. */ - private def popCopy(td: TokenData) = { + private def popCopy(td: TokenData) = if (following.isEmpty) td.token = EMPTY else { td.copyFrom(following.head) From 05126a2257edd871163e4e6a233a490fd64f6149 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 15:33:40 +0200 Subject: [PATCH 4/8] Remove stray empty line --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index abd957573b8d..30c133410afc 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1351,7 +1351,6 @@ object Parsers { } else inParens(if (in.token == RPAREN) Nil else commaSeparated(argumentExpr)) - } /** ArgumentExprs ::= ParArgumentExprs From 55b512d3a855932294d4b86f9f094e32d8a2e760 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 15:38:04 +0200 Subject: [PATCH 5/8] Delete message class This error can no longer be raised. --- .../reporting/diagnostic/ErrorMessageID.java | 2 +- .../dotc/reporting/diagnostic/messages.scala | 19 ++----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index ef97720368ce..008d70bfcf06 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -50,7 +50,7 @@ public enum ErrorMessageID { ExpectedTokenButFoundID, MixedLeftAndRightAssociativeOpsID, CantInstantiateAbstractClassOrTraitID, - AnnotatedPrimaryConstructorRequiresModifierOrThisID, + DUMMY_AVAILABLE_1, OverloadedOrRecursiveMethodNeedsResultTypeID, RecursiveValueNeedsResultTypeID, CyclicReferenceInvolvingID, diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index f46030965681..39b807f55d79 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1135,21 +1135,6 @@ object messages { |""".stripMargin } - case class AnnotatedPrimaryConstructorRequiresModifierOrThis(cls: Name)(implicit ctx: Context) - extends Message(AnnotatedPrimaryConstructorRequiresModifierOrThisID) { - val kind = "Syntax" - val msg = hl"""${"private"}, ${"protected"}, or ${"this"} expected for annotated primary constructor""" - val explanation = - hl"""|When using annotations with a primary constructor of a class, - |the annotation must be followed by an access modifier - |(${"private"} or ${"protected"}) or ${"this"}. - | - |For example: - | ${"class Sample @deprecated this(param: Parameter) { ..."} - | ^^^^ - |""".stripMargin - } - case class OverloadedOrRecursiveMethodNeedsResultType(tree: Names.TermName)(implicit ctx: Context) extends Message(OverloadedOrRecursiveMethodNeedsResultTypeID) { val kind = "Syntax" @@ -1277,7 +1262,7 @@ object messages { |$noParameters""".stripMargin } - + case class AmbiguousOverload(tree: tpd.Tree, alts: List[SingleDenotation], pt: Type)( err: typer.ErrorReporting.Errors)( implicit ctx: Context) @@ -1296,7 +1281,7 @@ object messages { |- adding a type ascription as in `${"instance.myMethod: String => Int"}` |""" } - + case class ReassignmentToVal(name: Names.Name)(implicit ctx: Context) extends Message(ReassignmentToValID) { val kind = "Reference" From 5cbe6fac7e87bb60f851da7ff2e538cd0cb84a4d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 17:22:42 +0200 Subject: [PATCH 6/8] Delete test of removed error message --- .../dotc/reporting/ErrorMessagesTests.scala | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index cc0c53aad017..e97914b14b54 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -198,21 +198,6 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertTrue("expected trait", isTrait) } - @Test def constructorModifier = - checkMessagesAfter("frontend") { - """ - |class AnotherClass @deprecated () - """.stripMargin - } - .expect { (ictx, messages) => - implicit val ctx: Context = ictx - val defn = ictx.definitions - - assertMessageCount(1, messages) - val AnnotatedPrimaryConstructorRequiresModifierOrThis(cls) :: Nil = messages - assertEquals("AnotherClass", cls.show) - } - @Test def overloadedMethodNeedsReturnType = checkMessagesAfter("frontend") { """ @@ -399,7 +384,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals("Scope.foo(1)", tree.show) assertEquals("((a: Int)Unit)(Scope.foo)", methodPart.show) } - + @Test def ambiugousOverloadWithWildcard = checkMessagesAfter("frontend") { """object Context { From 6b8b7946bd20715df2986eb9f2cc3018c9c4fe47 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 15 May 2017 22:38:48 +0200 Subject: [PATCH 7/8] Treat first () as annotation argument To maintain compatibility with Scala2 we a `()` argument list as belonging to a primary constructor annotation if it is the first argument list for that annotation. --- .../dotty/tools/dotc/parsing/Parsers.scala | 20 ++++++++++--------- tests/pos/i2426.scala | 13 +++++++++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 30c133410afc..c679c7675ccd 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1318,9 +1318,9 @@ object Parsers { * | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')' * * Special treatment for arguments of primary class constructor - * annotations. Empty argument lists `(` `)` get represented - * as `List(ParamNotArg)` instead of `Nil`, indicating that the - * token sequence should be interpreted as an empty parameter clause + * annotations. All empty argument lists `(` `)` follwoing the first + * get represented as `List(ParamNotArg)` instead of `Nil`, indicating that + * the token sequence should be interpreted as an empty parameter clause * instead. `ParamNotArg` can also be produced when parsing the first * argument (see `classConstrAnnotExpr`). * @@ -1329,13 +1329,15 @@ object Parsers { * contains the tokens that need to be replayed to parse the parameter clause. * Otherwise, `lookaheadTokens` is empty. */ - def parArgumentExprs(): List[Tree] = { + def parArgumentExprs(first: Boolean = false): List[Tree] = { if (inClassConstrAnnots) { assert(lookaheadTokens.isEmpty) saveLookahead() accept(LPAREN) val args = - if (in.token == RPAREN) ParamNotArg :: Nil + if (in.token == RPAREN) + if (first) Nil // first () counts as annotation argument + else ParamNotArg :: Nil else { openParens.change(LPAREN, +1) try commaSeparated(argumentExpr) @@ -1377,8 +1379,8 @@ object Parsers { * Otherwise parse as normal. */ def classConstrAnnotExpr() = { - saveLookahead() if (in.token == IDENTIFIER) { + saveLookahead() postfixExpr() match { case Ident(_) if in.token == COLON => ParamNotArg case t => expr1Rest(t, Location.InParens) @@ -1404,9 +1406,9 @@ object Parsers { */ def parArgumentExprss(fn: Tree): Tree = if (in.token == LPAREN) { - val first = parArgumentExprs() - if (inClassConstrAnnots && first == ParamNotArg :: Nil) fn - else parArgumentExprss(Apply(fn, first)) + val args = parArgumentExprs(first = !fn.isInstanceOf[Trees.Apply[_]]) + if (inClassConstrAnnots && args == ParamNotArg :: Nil) fn + else parArgumentExprss(Apply(fn, args)) } else fn diff --git a/tests/pos/i2426.scala b/tests/pos/i2426.scala index 9ab22267d47f..7910e3d786a0 100644 --- a/tests/pos/i2426.scala +++ b/tests/pos/i2426.scala @@ -2,7 +2,18 @@ class Foo @deprecated("foo", "2.11.0") (x: Int) class Bar @deprecated(x: Int) -class Baz @deprecated() +class Baz1 @deprecated(implicit c: C) +class Baz2 @deprecated()(implicit c: C) +class Baz3 @deprecated()()(implicit c: C) + +object Test { + implicit val c: C = obj + new Baz1 + new Baz2 + new Baz3() +} + +class D(implicit x: C) class C object obj extends C From 0725a61c86ea973b6b05a9e09a3456ea7b980ac4 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 23 May 2017 11:03:53 +0200 Subject: [PATCH 8/8] Fix a typo --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c679c7675ccd..0b0638be29f2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1318,7 +1318,7 @@ object Parsers { * | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')' * * Special treatment for arguments of primary class constructor - * annotations. All empty argument lists `(` `)` follwoing the first + * annotations. All empty argument lists `(` `)` following the first * get represented as `List(ParamNotArg)` instead of `Nil`, indicating that * the token sequence should be interpreted as an empty parameter clause * instead. `ParamNotArg` can also be produced when parsing the first