Skip to content

Fix #2515: Fix handling constructor annotation arguments #3757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 31 additions & 100 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,39 +306,16 @@ 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[this] 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[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) =
Expand Down Expand Up @@ -1371,80 +1348,22 @@ 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
*/
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 = {
Expand All @@ -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 `( <annot>* <mod>* 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
Expand Down
38 changes: 5 additions & 33 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/pos/i2426.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)