diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index fdecb3876ac7..2436ad4081f2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -306,9 +306,6 @@ 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 */ @@ -316,29 +313,9 @@ object Parsers { 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[this] var lookaheadTokens = new ListBuffer[TokenData] - - /** Copy current token to end of lookahead */ - private def saveLookahead() = { - val lookahead = new TokenData{} - lookahead.copyFrom(in) - lookaheadTokens += lookahead + inClassConstrAnnots = true + try body + finally inClassConstrAnnots = saved } def migrationWarningOrError(msg: String, offset: Int = in.offset) = @@ -1371,44 +1348,9 @@ object Parsers { /** ParArgumentExprs ::= `(' [ExprsInParens] `)' * | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')' - * - * Special treatment for arguments of primary class constructor - * 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 - * 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(first: Boolean = false): List[Tree] = { - if (inClassConstrAnnots) { - assert(lookaheadTokens.isEmpty) - saveLookahead() - accept(LPAREN) - val args = - if (in.token == RPAREN) - if (first) Nil // first () counts as annotation argument - else 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)) - } + */ + def parArgumentExprs(): List[Tree] = + inParens(if (in.token == RPAREN) Nil else commaSeparated(argumentExpr)) /** ArgumentExprs ::= ParArgumentExprs * | [nl] BlockExpr @@ -1416,35 +1358,12 @@ object Parsers { def argumentExprs(): List[Tree] = if (in.token == LBRACE) blockExpr() :: Nil else parArgumentExprs() - 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 parse as normal. - */ - def classConstrAnnotExpr() = { - if (in.token == IDENTIFIER) { - saveLookahead() - postfixExpr() match { - case Ident(_) if in.token == COLON => ParamNotArg - case t => expr1Rest(t, Location.InParens) - } - } - else if (isExprIntro) exprInParens() - else ParamNotArg + val argumentExpr = () => exprInParens() match { + case arg @ Assign(Ident(id), rhs) => cpy.NamedArg(arg)(id, rhs) + case arg => arg } + /** ArgumentExprss ::= {ArgumentExprs} */ def argumentExprss(fn: Tree): Tree = { @@ -1455,17 +1374,29 @@ 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) { - val args = parArgumentExprs(first = !fn.isInstanceOf[Trees.Apply[_]]) - if (inClassConstrAnnots && args == ParamNotArg :: Nil) fn - else parArgumentExprss(Apply(fn, args)) + * Special treatment for arguments to primary constructor annotations. + * (...) is considered an argument only if it does not look like a formal + * parameter list, i.e. does not start with `( * * ident : ` + * Furthermore, `()` is considered a annotation argument only if it comes first. + */ + def parArgumentExprss(fn: Tree): Tree = { + def isLegalAnnotArg: Boolean = { + val lookahead = in.lookaheadScanner + (lookahead.token == LPAREN) && { + lookahead.nextToken() + if (lookahead.token == RPAREN) + !fn.isInstanceOf[Trees.Apply[_]] // allow one () as annotation argument + else if (lookahead.token == IDENTIFIER) { + lookahead.nextToken() + lookahead.token != COLON + } + else canStartExpressionTokens.contains(lookahead.token) + } } + if (in.token == LPAREN && (!inClassConstrAnnots || isLegalAnnotArg)) + parArgumentExprss(Apply(fn, parArgumentExprs())) else fn + } /** BlockExpr ::= `{' BlockExprContents `}' * BlockExprContents ::= CaseClauses | Block diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 15265e4ca5cb..034a394b4a95 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -216,42 +216,11 @@ object Scanners { private class TokenData0 extends TokenData - /** The scanner itself needs one token lookahead and one token history + /** We need 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[this] 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 @@ -326,6 +295,9 @@ object Scanners { case _ => } + /** A new Scanner that starts at the current token offset */ + def lookaheadScanner = new Scanner(source, offset) + /** Produce next token, filling TokenData fields of Scanner. */ def nextToken(): Unit = { @@ -340,7 +312,7 @@ object Scanners { if (token == ERROR) adjustSepRegions(STRINGLIT) } else { this copyFrom next - popCopy(next) + next.token = EMPTY } /** Insert NEWLINE or NEWLINES if diff --git a/tests/pos/i2426.scala b/tests/pos/i2426.scala index 7910e3d786a0..9bd0500c0a72 100644 --- a/tests/pos/i2426.scala +++ b/tests/pos/i2426.scala @@ -22,3 +22,7 @@ class ann(x: C)(y: C, s: String) extends scala.annotation.Annotation class Bam @ann(obj)(obj, "h")(n: String) +// #2515 +class Foo2 @deprecated() (@deprecated() id: String) + +