Skip to content

Commit

Permalink
Merge pull request #8781 from dwijnand/2.12/annotation-parsing-and-De…
Browse files Browse the repository at this point in the history
…precated
  • Loading branch information
lrytz committed May 8, 2020
2 parents d5b7f53 + 3c89087 commit 85ec3c5
Show file tree
Hide file tree
Showing 37 changed files with 474 additions and 51 deletions.
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] = {
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 @@ -822,11 +822,9 @@ abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
in.skip(attrLen)

case tpnme.DeprecatedATTR =>
val arg = Literal(Constant("see corresponding Javadoc for more information."))
sym.addAnnotation(DeprecatedAttr, arg, Literal(Constant("")))
in.skip(attrLen)
if (sym == clazz)
staticModule.addAnnotation(DeprecatedAttr, arg, Literal(Constant("")))
staticModule.addAnnotation(JavaDeprecatedAttr)

case tpnme.ConstantValueATTR =>
completer.constant = pool.getConstant(u2)
Expand All @@ -852,8 +850,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)

// 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. */
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
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/Definitions.scala
Expand Up @@ -1198,6 +1198,7 @@ trait Definitions extends api.StandardDefinitions {
lazy val UncheckedBoundsClass = getClassIfDefined("scala.reflect.internal.annotations.uncheckedBounds")
lazy val UnspecializedClass = requiredClass[scala.annotation.unspecialized]
lazy val VolatileAttr = requiredClass[scala.volatile]
lazy val JavaDeprecatedAttr = requiredClass[java.lang.Deprecated]
lazy val FunctionalInterfaceClass = requiredClass[java.lang.FunctionalInterface]

// Meta-annotations
Expand Down
2 changes: 1 addition & 1 deletion src/reflect/scala/reflect/internal/Symbols.scala
Expand Up @@ -899,7 +899,7 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
def isStrictFP: Boolean = !isDeferred && (hasAnnotation(ScalaStrictFPAttr) || originalOwner.isStrictFP)
def isSerializable = info.baseClasses.exists(p => p == SerializableClass || p == JavaSerializableClass)
def hasBridgeAnnotation = hasAnnotation(BridgeClass)
def isDeprecated = hasAnnotation(DeprecatedAttr)
def isDeprecated = hasAnnotation(DeprecatedAttr) || (isJava && hasAnnotation(JavaDeprecatedAttr))
def deprecationMessage = getAnnotation(DeprecatedAttr) flatMap (_ stringArg 0)
def deprecationVersion = getAnnotation(DeprecatedAttr) flatMap (_ stringArg 1)
def deprecatedParamName = getAnnotation(DeprecatedNameAttr) flatMap (_ symbolArg 0 orElse Some(nme.NO_NAME))
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Expand Up @@ -429,6 +429,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
definitions.UncheckedBoundsClass
definitions.UnspecializedClass
definitions.VolatileAttr
definitions.JavaDeprecatedAttr
definitions.FunctionalInterfaceClass
definitions.BeanGetterTargetClass
definitions.BeanSetterTargetClass
Expand Down
1 change: 0 additions & 1 deletion test/files/jvm/deprecation.check
@@ -1,3 +1,2 @@
warning: there were four deprecation warnings; re-run with -deprecation for details
Note: deprecation/Use_2.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
4 changes: 2 additions & 2 deletions test/files/neg/t10752.check
@@ -1,7 +1,7 @@
Test_2.scala:2: warning: class DeprecatedClass in package p1 is deprecated: see corresponding Javadoc for more information.
Test_2.scala:2: warning: class DeprecatedClass in package p1 is deprecated
def useC = p1.DeprecatedClass.foo
^
Test_2.scala:3: warning: method foo in class DeprecatedMethod is deprecated: see corresponding Javadoc for more information.
Test_2.scala:3: warning: method foo in class DeprecatedMethod is deprecated
def useM = p1.DeprecatedMethod.foo
^
error: No warnings can be incurred under -Xfatal-warnings.
Expand Down
9 changes: 9 additions & 0 deletions test/files/neg/t9617.check
@@ -0,0 +1,9 @@
Test.scala:3: warning: class DeprecatedClass in package p1 is deprecated
def useC = p1.DeprecatedClass.foo
^
Test.scala:4: warning: method foo in class DeprecatedMethod is deprecated
def useM = p1.DeprecatedMethod.foo
^
error: No warnings can be incurred under -Xfatal-warnings.
two warnings found
one error found

0 comments on commit 85ec3c5

Please sign in to comment.