Skip to content

Commit eb1a686

Browse files
oderskyallanrenucci
authored andcommitted
Fix #2515: Fix handling constructor annotation arguments
When doing the indentation-based syntax, I found a better hammer: We can simply copy the Scanner at the current offset to create a lookahead Scanner. With that, the logic for handling primary constructor arguments could be greatly simplified. Most of #2432 could be reverted.
1 parent 7cd176f commit eb1a686

File tree

3 files changed

+40
-133
lines changed

3 files changed

+40
-133
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 31 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -306,39 +306,16 @@ object Parsers {
306306
} finally inFunReturnType = saved
307307
}
308308

309-
/** A placeholder for dummy arguments that should be re-parsed as parameters */
310-
val ParamNotArg = EmptyTree
311-
312309
/** A flag indicating we are parsing in the annotations of a primary
313310
* class constructor
314311
*/
315312
private[this] var inClassConstrAnnots = false
316313

317314
private def fromWithinClassConstr[T](body: => T): T = {
318315
val saved = inClassConstrAnnots
319-
try {
320-
inClassConstrAnnots = true
321-
body
322-
} finally {
323-
inClassConstrAnnots = saved
324-
if (lookaheadTokens.nonEmpty) {
325-
in.insertTokens(lookaheadTokens.toList)
326-
lookaheadTokens.clear()
327-
}
328-
}
329-
}
330-
331-
/** Lookahead tokens for the case of annotations in class constructors.
332-
* We store tokens in lookahead as long as they can form a valid prefix
333-
* of a class parameter clause.
334-
*/
335-
private[this] var lookaheadTokens = new ListBuffer[TokenData]
336-
337-
/** Copy current token to end of lookahead */
338-
private def saveLookahead() = {
339-
val lookahead = new TokenData{}
340-
lookahead.copyFrom(in)
341-
lookaheadTokens += lookahead
316+
inClassConstrAnnots = true
317+
try body
318+
finally inClassConstrAnnots = saved
342319
}
343320

344321
def migrationWarningOrError(msg: String, offset: Int = in.offset) =
@@ -1371,80 +1348,22 @@ object Parsers {
13711348

13721349
/** ParArgumentExprs ::= `(' [ExprsInParens] `)'
13731350
* | `(' [ExprsInParens `,'] PostfixExpr `:' `_' `*' ')'
1374-
*
1375-
* Special treatment for arguments of primary class constructor
1376-
* annotations. All empty argument lists `(` `)` following the first
1377-
* get represented as `List(ParamNotArg)` instead of `Nil`, indicating that
1378-
* the token sequence should be interpreted as an empty parameter clause
1379-
* instead. `ParamNotArg` can also be produced when parsing the first
1380-
* argument (see `classConstrAnnotExpr`).
1381-
*
1382-
* The method affects `lookaheadTokens` as a side effect.
1383-
* If the argument list parses as `List(ParamNotArg)`, `lookaheadTokens`
1384-
* contains the tokens that need to be replayed to parse the parameter clause.
1385-
* Otherwise, `lookaheadTokens` is empty.
1386-
*/
1387-
def parArgumentExprs(first: Boolean = false): List[Tree] = {
1388-
if (inClassConstrAnnots) {
1389-
assert(lookaheadTokens.isEmpty)
1390-
saveLookahead()
1391-
accept(LPAREN)
1392-
val args =
1393-
if (in.token == RPAREN)
1394-
if (first) Nil // first () counts as annotation argument
1395-
else ParamNotArg :: Nil
1396-
else {
1397-
openParens.change(LPAREN, +1)
1398-
try commaSeparated(argumentExpr)
1399-
finally openParens.change(LPAREN, -1)
1400-
}
1401-
if (args == ParamNotArg :: Nil)
1402-
in.adjustSepRegions(RPAREN) // simulate `)` without requiring it
1403-
else {
1404-
lookaheadTokens.clear()
1405-
accept(RPAREN)
1406-
}
1407-
args
1408-
}
1409-
else
1410-
inParens(if (in.token == RPAREN) Nil else commaSeparated(argumentExpr))
1411-
}
1351+
*/
1352+
def parArgumentExprs(): List[Tree] =
1353+
inParens(if (in.token == RPAREN) Nil else commaSeparated(argumentExpr))
14121354

14131355
/** ArgumentExprs ::= ParArgumentExprs
14141356
* | [nl] BlockExpr
14151357
*/
14161358
def argumentExprs(): List[Tree] =
14171359
if (in.token == LBRACE) blockExpr() :: Nil else parArgumentExprs()
14181360

1419-
val argumentExpr = () => {
1420-
val arg =
1421-
if (inClassConstrAnnots && lookaheadTokens.nonEmpty) classConstrAnnotExpr()
1422-
else exprInParens()
1423-
arg match {
1424-
case arg @ Assign(Ident(id), rhs) => cpy.NamedArg(arg)(id, rhs)
1425-
case arg => arg
1426-
}
1427-
}
1428-
1429-
/** Handle first argument of an argument list to an annotation of
1430-
* a primary class constructor. If the current token either cannot
1431-
* start an expression or is an identifier and is followed by `:`,
1432-
* stop parsing the rest of the expression and return `EmptyTree`,
1433-
* indicating that we should re-parse the expression as a parameter clause.
1434-
* Otherwise parse as normal.
1435-
*/
1436-
def classConstrAnnotExpr() = {
1437-
if (in.token == IDENTIFIER) {
1438-
saveLookahead()
1439-
postfixExpr() match {
1440-
case Ident(_) if in.token == COLON => ParamNotArg
1441-
case t => expr1Rest(t, Location.InParens)
1442-
}
1443-
}
1444-
else if (isExprIntro) exprInParens()
1445-
else ParamNotArg
1361+
val argumentExpr = () => exprInParens() match {
1362+
case arg @ Assign(Ident(id), rhs) => cpy.NamedArg(arg)(id, rhs)
1363+
case arg => arg
14461364
}
14471365

1366+
14481367
/** ArgumentExprss ::= {ArgumentExprs}
14491368
*/
14501369
def argumentExprss(fn: Tree): Tree = {
@@ -1455,17 +1374,29 @@ object Parsers {
14551374

14561375
/** ParArgumentExprss ::= {ParArgumentExprs}
14571376
*
1458-
* Special treatment for arguments of primary class constructor
1459-
* annotations. If an argument list returns `List(ParamNotArg)`
1460-
* ignore it, and return prefix parsed before that list instead.
1461-
*/
1462-
def parArgumentExprss(fn: Tree): Tree =
1463-
if (in.token == LPAREN) {
1464-
val args = parArgumentExprs(first = !fn.isInstanceOf[Trees.Apply[_]])
1465-
if (inClassConstrAnnots && args == ParamNotArg :: Nil) fn
1466-
else parArgumentExprss(Apply(fn, args))
1377+
* Special treatment for arguments to primary constructor annotations.
1378+
* (...) is considered an argument only if it does not look like a formal
1379+
* parameter list, i.e. does not start with `( <annot>* <mod>* ident : `
1380+
* Furthermore, `()` is considered a annotation argument only if it comes first.
1381+
*/
1382+
def parArgumentExprss(fn: Tree): Tree = {
1383+
def isLegalAnnotArg: Boolean = {
1384+
val lookahead = in.lookaheadScanner
1385+
(lookahead.token == LPAREN) && {
1386+
lookahead.nextToken()
1387+
if (lookahead.token == RPAREN)
1388+
!fn.isInstanceOf[Trees.Apply[_]] // allow one () as annotation argument
1389+
else if (lookahead.token == IDENTIFIER) {
1390+
lookahead.nextToken()
1391+
lookahead.token != COLON
1392+
}
1393+
else canStartExpressionTokens.contains(lookahead.token)
1394+
}
14671395
}
1396+
if (in.token == LPAREN && (!inClassConstrAnnots || isLegalAnnotArg))
1397+
parArgumentExprss(Apply(fn, parArgumentExprs()))
14681398
else fn
1399+
}
14691400

14701401
/** BlockExpr ::= `{' BlockExprContents `}'
14711402
* BlockExprContents ::= CaseClauses | Block

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -216,42 +216,11 @@ object Scanners {
216216

217217
private class TokenData0 extends TokenData
218218

219-
/** The scanner itself needs one token lookahead and one token history
219+
/** We need one token lookahead and one token history
220220
*/
221221
val next : TokenData = new TokenData0
222222
private val prev : TokenData = new TokenData0
223223

224-
/** The parser can also add more lookahead tokens via `insertTokens`.
225-
* Tokens beyond `next` are stored in `following`.
226-
*/
227-
private[this] var following: List[TokenData] = Nil
228-
229-
/** Push a copy of token data `td` to `following` */
230-
private def pushCopy(td: TokenData) = {
231-
val copy = new TokenData0
232-
copy.copyFrom(td)
233-
following = copy :: following
234-
}
235-
236-
/** If following is empty, invalidate token data `td` by setting
237-
* `td.token` to `EMPTY`. Otherwise pop head of `following` into `td`.
238-
*/
239-
private def popCopy(td: TokenData) =
240-
if (following.isEmpty) td.token = EMPTY
241-
else {
242-
td.copyFrom(following.head)
243-
following = following.tail
244-
}
245-
246-
/** Insert tokens `tds` in front of current token */
247-
def insertTokens(tds: List[TokenData]) = {
248-
if (next.token != EMPTY) pushCopy(next)
249-
pushCopy(this)
250-
following = tds ++ following
251-
popCopy(this)
252-
if (following.nonEmpty) popCopy(next)
253-
}
254-
255224
/** a stack of tokens which indicates whether line-ends can be statement separators
256225
* also used for keeping track of nesting levels.
257226
* We keep track of the closing symbol of a region. This can be
@@ -326,6 +295,9 @@ object Scanners {
326295
case _ =>
327296
}
328297

298+
/** A new Scanner that starts at the current token offset */
299+
def lookaheadScanner = new Scanner(source, offset)
300+
329301
/** Produce next token, filling TokenData fields of Scanner.
330302
*/
331303
def nextToken(): Unit = {
@@ -340,7 +312,7 @@ object Scanners {
340312
if (token == ERROR) adjustSepRegions(STRINGLIT)
341313
} else {
342314
this copyFrom next
343-
popCopy(next)
315+
next.token = EMPTY
344316
}
345317

346318
/** Insert NEWLINE or NEWLINES if

tests/pos/i2426.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@ class ann(x: C)(y: C, s: String) extends scala.annotation.Annotation
2222

2323
class Bam @ann(obj)(obj, "h")(n: String)
2424

25+
// #2515
26+
class Foo2 @deprecated() (@deprecated() id: String)
27+
28+

0 commit comments

Comments
 (0)