Skip to content
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

Parse annotation in jointly compiled Java source files and honour @Deprecated #8781

Merged
merged 2 commits into from May 8, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 17 additions & 14 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Expand Up @@ -95,6 +95,23 @@ trait ParsersCommon extends ScannersCommon { self =>
*/
@inline final def makeParens(body: => List[Tree]): Parens =
Parens(inParens(if (in.token == RPAREN) Nil else body))

/** {{{ part { `sep` part } }}}, or if sepFirst is true, {{{ { `sep` part } }}}. */
final def tokenSeparated[T](separator: Token, sepFirst: Boolean, part: => T): List[T] = {
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
val ts = new ListBuffer[T]
if (!sepFirst)
ts += part

while (in.token == separator) {
in.nextToken()
ts += part
}
ts.toList
}

/** {{{ tokenSeparated }}}, with the separator fixed to commas. */
@inline final def commaSeparated[T](part: => T): List[T] =
tokenSeparated(COMMA, sepFirst = false, part)
}
}

Expand Down Expand Up @@ -791,20 +808,6 @@ self =>
errorTypeTree
}
}

/** {{{ part { `sep` part } }}},or if sepFirst is true, {{{ { `sep` part } }}}. */
final def tokenSeparated[T](separator: Token, sepFirst: Boolean, part: => T): List[T] = {
val ts = new ListBuffer[T]
if (!sepFirst)
ts += part

while (in.token == separator) {
in.nextToken()
ts += part
}
ts.toList
}
@inline final def commaSeparated[T](part: => T): List[T] = tokenSeparated(COMMA, sepFirst = false, part)
@inline final def caseSeparated[T](part: => T): List[T] = tokenSeparated(CASE, sepFirst = true, part)
def readAnnots(part: => Tree): List[Tree] = tokenSeparated(AT, sepFirst = true, part)

Expand Down
3 changes: 3 additions & 0 deletions src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Expand Up @@ -40,6 +40,9 @@ trait ScannersCommon {
}

trait ScannerCommon extends CommonTokenData {
/** Consume and discard the next token. */
def nextToken(): Unit

// things to fill in, in addition to buf, decodeUni which come from CharArrayReader
def error(off: Offset, msg: String): Unit
def incompleteInputError(off: Offset, msg: String): Unit
Expand Down
108 changes: 86 additions & 22 deletions src/compiler/scala/tools/nsc/javac/JavaParsers.scala
Expand Up @@ -242,11 +242,21 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {

// -------------------- specific parsing routines ------------------

def qualId(): RefTree = {
var t: RefTree = atPos(in.currentPos) { Ident(ident()) }
while (in.token == DOT) {
def qualId(orClassLiteral: Boolean = false): Tree = {
var t: Tree = atPos(in.currentPos) { Ident(ident()) }
var done = false
while (!done && in.token == DOT) {
in.nextToken()
t = atPos(in.currentPos) { Select(t, ident()) }
t = atPos(in.currentPos) {
if (orClassLiteral && in.token == CLASS) {
in.nextToken()
done = true
val tpeArg = convertToTypeId(t)
TypeApply(Select(gen.mkAttributedRef(definitions.PredefModule), nme.classOf), tpeArg :: Nil)
} else {
Select(t, ident())
}
}
}
t
}
Expand Down Expand Up @@ -275,7 +285,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}

def typ(): Tree = {
annotations()
annotations() // TODO: fix scala/bug#9883 (JSR 308)
optArrayBrackets {
if (in.token == FINAL) in.nextToken()
if (in.token == IDENTIFIER) {
Expand Down Expand Up @@ -334,20 +344,73 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}

def annotations(): List[Tree] = {
//var annots = new ListBuffer[Tree]
val annots = new ListBuffer[Tree]
while (in.token == AT) {
in.nextToken()
annotation()
val annot = annotation()
if (annot.nonEmpty) annots += annot
}
List() // don't pass on annotations for now
annots.toList
}

/** Annotation ::= TypeName [`(` AnnotationArgument {`,` AnnotationArgument} `)`]
/** Annotation ::= TypeName [`(` [AnnotationArgument {`,` AnnotationArgument}] `)`]
*/
def annotation() {
qualId()
if (in.token == LPAREN) { skipAhead(); accept(RPAREN) }
else if (in.token == LBRACE) { skipAhead(); accept(RBRACE) }
def annotation(): Tree = {
def annArg(): Tree = {
def annVal(): Tree = {
tryLiteral() match {
case Some(lit) => atPos(in.currentPos)(Literal(lit))
case _ if in.token == AT =>
in.nextToken()
annotation()
case _ if in.token == LBRACE =>
atPos(in.pos) {
val elts = inBracesOrNil(commaSeparated(annVal()))
if (elts.exists(_.isEmpty)) EmptyTree
else Apply(ArrayModule_overloadedApply, elts: _*)
}
case _ if in.token == IDENTIFIER =>
qualId(orClassLiteral = true)
}
}

if (in.token == IDENTIFIER) {
qualId(orClassLiteral = true) match {
case name: Ident if in.token == EQUALS =>
in.nextToken()
/* name = value */
val value = annVal()
if (value.isEmpty) EmptyTree else gen.mkNamedArg(name, value)
case rhs =>
/* implicit `value` arg with constant value */
gen.mkNamedArg(nme.value, rhs)
}
} else {
/* implicit `value` arg */
val value = annVal()
if (value.isEmpty) EmptyTree else gen.mkNamedArg(nme.value, value)
}
}

atPos(in.pos) {
val id = convertToTypeId(qualId())
if (in.token == LPAREN) {
val saved = new JavaTokenData {}.copyFrom(in) // prep to bail if non-literals/identifiers
accept(LPAREN)
val args =
if (in.token == RPAREN) Nil
else commaSeparated(atPos(in.pos)(annArg()))
if (in.token == RPAREN) {
accept(RPAREN)
New(id, args :: Nil)
} else {
in.copyFrom(saved)
skipAhead()
accept(RPAREN)
EmptyTree
}
} else New(id, ListOfNil)
}
}

def modifiers(inInterface: Boolean): Modifiers = {
Expand All @@ -361,7 +424,8 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
in.token match {
case AT if (in.lookaheadToken != INTERFACE) =>
in.nextToken()
annotation()
val annot = annotation()
if (annot.nonEmpty) annots :+= annot
case PUBLIC =>
isPackageAccess = false
in.nextToken()
Expand Down Expand Up @@ -419,10 +483,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {

def typeParam(): TypeDef =
atPos(in.currentPos) {
annotations()
val anns = annotations()
val name = identForType()
val hi = if (in.token == EXTENDS) { in.nextToken() ; bound() } else EmptyTree
TypeDef(Modifiers(Flags.JAVA | Flags.DEFERRED | Flags.PARAM), name, Nil, TypeBoundsTree(EmptyTree, hi))
TypeDef(Modifiers(Flags.JAVA | Flags.DEFERRED | Flags.PARAM, tpnme.EMPTY, anns), name, Nil, TypeBoundsTree(EmptyTree, hi))
}

def bound(): Tree =
Expand All @@ -446,15 +510,15 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {

def formalParam(): ValDef = {
if (in.token == FINAL) in.nextToken()
annotations()
val anns = annotations()
var t = typ()
if (in.token == DOTDOTDOT) {
in.nextToken()
t = atPos(t.pos) {
AppliedTypeTree(scalaDot(tpnme.JAVA_REPEATED_PARAM_CLASS_NAME), List(t))
}
}
varDecl(in.currentPos, Modifiers(Flags.JAVA | Flags.PARAM), t, ident().toTermName)
varDecl(in.currentPos, Modifiers(Flags.JAVA | Flags.PARAM, typeNames.EMPTY, anns), t, ident().toTermName)
}

def optThrows() {
Expand Down Expand Up @@ -841,7 +905,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
}

def enumConst(enumType: Tree): (ValDef, Boolean) = {
annotations()
val anns = annotations()
var hasClassBody = false
val res = atPos(in.currentPos) {
val name = ident()
Expand All @@ -856,7 +920,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
skipAhead()
accept(RBRACE)
}
ValDef(Modifiers(Flags.JAVA_ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC), name.toTermName, enumType, blankExpr)
ValDef(Modifiers(Flags.JAVA_ENUM | Flags.STABLE | Flags.JAVA | Flags.STATIC, typeNames.EMPTY, anns), name.toTermName, enumType, blankExpr)
}
(res, hasClassBody)
}
Expand Down Expand Up @@ -894,10 +958,10 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners {
var pos = in.currentPos
val pkg: RefTree =
if (in.token == AT || in.token == PACKAGE) {
annotations()
annotations() // TODO: put these somewhere?
pos = in.currentPos
accept(PACKAGE)
val pkg = qualId()
val pkg = qualId().asInstanceOf[RefTree]
accept(SEMI)
pkg
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/scala/tools/nsc/javac/JavaScanners.scala
Expand Up @@ -52,12 +52,13 @@ trait JavaScanners extends ast.parser.ScannersCommon {
/** the base of a number */
var base: Int = 0

def copyFrom(td: JavaTokenData) = {
def copyFrom(td: JavaTokenData): this.type = {
this.token = td.token
this.pos = td.pos
this.lastPos = td.lastPos
this.name = td.name
this.base = td.base
this
}
}

Expand Down
Expand Up @@ -851,8 +851,15 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {

case tpnme.RuntimeAnnotationATTR =>
val numAnnots = u2
val annots = new ListBuffer[AnnotationInfo]
for (n <- 0 until numAnnots; annot <- parseAnnotation(u2))
sym.addAnnotation(annot)
annots += annot
/* `sym.withAnnotations(annots)`, like `sym.addAnnotation(annot)`, prepends,
* so if we parsed in classfile order we would wind up with the annotations
* in reverse order in `sym.annotations`. Instead we just read them out the
* other way around, for now. TODO: sym.addAnnotation add to the end?
*/
sym.setAnnotations(sym.annotations ::: annots.toList)
hrhino marked this conversation as resolved.
Show resolved Hide resolved

// TODO 1: parse runtime visible annotations on parameters
// case tpnme.RuntimeParamAnnotationATTR
Expand Down
11 changes: 10 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -3807,6 +3807,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
*/
def typedAnnotation(ann: Tree, mode: Mode = EXPRmode): AnnotationInfo = {
var hasError: Boolean = false
var unmappable: Boolean = false
val pending = ListBuffer[AbsTypeError]()
def ErroneousAnnotation = new ErroneousAnnotation().setOriginal(ann)

Expand Down Expand Up @@ -3840,7 +3841,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}

if (const == null) {
reportAnnotationError(AnnotationNotAConstantError(ttree)); None
if (unit.isJava) unmappable = true
else reportAnnotationError(AnnotationNotAConstantError(ttree))
None
} else if (const.value == null) {
reportAnnotationError(AnnotationArgNullError(tr)); None
} else
Expand Down Expand Up @@ -3887,6 +3890,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case Typed(t, _) =>
tree2ConstArg(t, pt)

case tree if unit.isJava && pt.typeSymbol == ArrayClass =>
/* If we get here, we have a Java array annotation argument which was passed
* as a single value, and needs to be wrapped. */
lrytz marked this conversation as resolved.
Show resolved Hide resolved
trees2ConstArg(tree :: Nil, pt.typeArgs.head)

case tree =>
tryConst(tree, pt)
}
Expand Down Expand Up @@ -3964,6 +3972,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}

if (hasError) ErroneousAnnotation
else if (unmappable) UnmappableAnnotation
else AnnotationInfo(annType, List(), nvPairs map {p => (p._1, p._2.get)}).setOriginal(Apply(typedFun, args).setPos(ann.pos))
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/interactive/scala/tools/nsc/interactive/Global.scala
Expand Up @@ -1369,7 +1369,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "")
val symbols =
Set(UnitClass, BooleanClass, ByteClass,
ShortClass, IntClass, LongClass, FloatClass,
DoubleClass, NilModule, ListClass) ++ TupleClass.seq
DoubleClass, NilModule, ListClass, PredefModule) ++ TupleClass.seq ++ ArrayModule_overloadedApply.alternatives
symbols.foreach(_.initialize)
}

Expand Down
4 changes: 4 additions & 0 deletions src/partest-extras/scala/tools/partest/BytecodeTest.scala
Expand Up @@ -124,6 +124,10 @@ abstract class BytecodeTest {
}

// loading
protected def getField(classNode: ClassNode, name: String): FieldNode =
classNode.fields.asScala.find(_.name == name) getOrElse
sys.error(s"Didn't find field '$name' in class '${classNode.name}'")

protected def getMethod(classNode: ClassNode, name: String): MethodNode =
classNode.methods.asScala.find(_.name == name) getOrElse
sys.error(s"Didn't find method '$name' in class '${classNode.name}'")
Expand Down
6 changes: 6 additions & 0 deletions test/files/presentation/parse-invariants/src/a/A.java
@@ -1,5 +1,10 @@
package syntax;

@NoArgs
@Empty()
@Simple(n = 1, c = '2', f = 6.7f, d = 8.9, s = "t", z = A.class, e = P.Pluto, a = @C.I("t"))
@Arrays({ @Array({0, 1, C._2}), @Array(3) })
@Deprecated
class A {
transient volatile int x;
strictfp void test() {
Expand All @@ -11,6 +16,7 @@ synchronized void syncMethod() {}

void thrower() throws Throwable {}

@Deprecated void deprecated() {}
}

strictfp class B {}
8 changes: 6 additions & 2 deletions test/files/run/t4788-separate-compilation.check
@@ -1,5 +1,9 @@
Some(@Ljava/lang/Deprecated;())
None
Some(@LSAnnotation;())
Some(@LCAnnotation;())
None
Some(@LCAnnotation;() // invisible)
Some(@LRAnnotation;())
Some(@LArgs_0$Ann;(value="a") // invisible)
Some(@LArgs_0$Ann;(value="ab") // invisible)
Some(@LArgs_0$Ann;(value="konst") // invisible)
Some(@LArgs_0$Ann;(value="nokst") // invisible)
13 changes: 13 additions & 0 deletions test/files/run/t4788-separate-compilation/Args_0.java
@@ -0,0 +1,13 @@
public class Args_0 {
public static @interface Ann {
String value();
}

public static final String konst = "konst";
public static final String nokst = "no" + "kst";

@Ann("a") public static int x1 = 0; // parsed
@Ann(Args_0.konst) public static int x3 = 0; // parsed
@Ann("a" + "b") public static int x2 = 0; // dropped
@Ann(Args_0.nokst) public static int x4 = 0; // dropped (error w/ type-checking)
}