Skip to content

Commit

Permalink
Typecheck ConstantAnnotations normally, not like Java annotations
Browse files Browse the repository at this point in the history
So far we handled ConstantAnnotations the same as Java annotations.
Instead of typechecking the full parse tree `Apply(New(annot), args)`,
the `New` and the individual `args` were typed individually. This is
needed for Java annotations as there's no corresponding constructor.
But for ConstantAnnotations we can just type check them normally and
then extract the constants.
  • Loading branch information
lrytz committed Dec 11, 2020
1 parent 95cb259 commit ebb5ab1
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 81 deletions.
4 changes: 2 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
Expand Up @@ -578,7 +578,7 @@ trait ContextErrors {
NormalTypeError(tree, "expected annotation of type " + expected + ", found " + found)

def MultipleArgumentListForAnnotationError(tree: Tree) =
NormalTypeError(tree, "multiple argument lists on Java annotation or subclass of ConstantAnnotation")
NormalTypeError(tree, "multiple argument lists on Java annotation")

def UnknownAnnotationNameError(tree: Tree, name: Name) =
NormalTypeError(tree, "unknown annotation argument name: " + name)
Expand All @@ -587,7 +587,7 @@ trait ContextErrors {
NormalTypeError(tree, "duplicate value for annotation argument " + name)

def ClassfileAnnotationsAsNamedArgsError(tree: Tree) =
NormalTypeError(tree, "arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments")
NormalTypeError(tree, "arguments to Java annotations have to be supplied as named arguments")

def AnnotationMissingArgError(tree: Tree, annType: Type, sym: Symbol) =
NormalTypeError(tree, "annotation " + annType.typeSymbol.fullName + " is missing argument " + sym.name)
Expand Down
66 changes: 47 additions & 19 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -3933,9 +3933,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}

def isDefaultArg(tree: Tree) = tree match {
case treeInfo.Applied(fun, _, _) => fun.symbol.isDefaultGetter
case _ => false
}

if (const == null) {
if (unit.isJava) unmappable = true
else reportAnnotationError(AnnotationNotAConstantError(ttree))
else if (!isDefaultArg(ttree)) reportAnnotationError(AnnotationNotAConstantError(ttree))
None
} else if (const.value == null) {
reportAnnotationError(AnnotationArgNullError(tr)); None
Expand Down Expand Up @@ -3969,7 +3974,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper

// use of Array.apply[T: ClassTag](xs: T*): Array[T]
// and Array.apply(x: Int, xs: Int*): Array[Int] (and similar)
case Apply(fun, args) =>
case treeInfo.Applied(fun, _, args :: _) =>
val typedFun = typed(fun, mode.forFunMode)
if (typedFun.symbol.owner == ArrayModule.moduleClass && typedFun.symbol.name == nme.apply)
pt match {
Expand Down Expand Up @@ -4026,13 +4031,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
} else {
// TODO: annType may have undetermined type params for Scala ConstantAnnotations, see scala/bug#11724.
// Can we infer them, e.g., `typed(argss.foldLeft(fun0)(Apply(_, _)))`?
val annScopeJava =
if (isJava) annType.decls.filter(sym => sym.isMethod && !sym.isConstructor && sym.isJavaDefined)
else EmptyScope // annScopeJava is only used if isJava
val annScopeJava = annType.decls.filter(sym => sym.isMethod && !sym.isConstructor && sym.isJavaDefined)

val names = mutable.Set[Symbol]()
names ++= (if (isJava) annScopeJava.iterator
else typedFun.tpe.params.iterator)
names ++= annScopeJava.iterator

def hasValue = names exists (_.name == nme.value)
val namedArgs = argss match {
Expand All @@ -4043,8 +4045,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper

val nvPairs = namedArgs map {
case arg @ NamedArg(Ident(name), rhs) =>
val sym = if (isJava) annScopeJava.lookup(name)
else findSymbol(typedFun.tpe.params)(_.name == name)
val sym = annScopeJava.lookup(name)
if (sym == NoSymbol) {
reportAnnotationError(UnknownAnnotationNameError(arg, name))
(nme.ERROR, None)
Expand All @@ -4053,7 +4054,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
(nme.ERROR, None)
} else {
names -= sym
if (isJava) sym.cookJavaRawInfo() // #3429
sym.cookJavaRawInfo() // #3429
val annArg = tree2ConstArg(rhs, sym.tpe.resultType)
(sym.name, annArg)
}
Expand Down Expand Up @@ -4086,10 +4087,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
AnnotationInfo(tpt.tpe, args, Nil).setOriginal(typedAnn).setPos(t.pos)

case Block(_, expr) =>
context.warning(t.pos, "Usage of named or default arguments transformed this annotation\n"+
"constructor call into a block. The corresponding AnnotationInfo\n"+
"will contain references to local values and default getters instead\n"+
"of the actual argument trees", WarningCategory.Other)
if (!annTypeSym.isNonBottomSubClass(ConstantAnnotationClass))
context.warning(t.pos, "Usage of named or default arguments transformed this annotation\n"+
"constructor call into a block. The corresponding AnnotationInfo\n"+
"will contain references to local values and default getters instead\n"+
"of the actual argument trees", WarningCategory.Other)
annInfo(expr)

case Apply(fun, args) =>
Expand Down Expand Up @@ -4124,12 +4126,38 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}

finish(
if (isJava || annTypeSym.isNonBottomSubClass(ConstantAnnotationClass))
finish {
if (isJava)
constantly
else
statically
)
else {
val info = statically
if (!info.isErroneous && annTypeSym.isNonBottomSubClass(ConstantAnnotationClass)) {
var namedArgs: Map[Name, Tree] = Map.empty
val treeInfo.Applied(constr, _, _) = info.original match {
case Block(stats, call) =>
// when named / default args are used
namedArgs = Map.from(stats collect {
case ValDef(_, name, _, rhs) => (name, rhs)
})
call
case call => call
}
val params = constr.symbol.paramss.headOption.getOrElse(Nil)
val assocs = info.args.zip(params) map {
case (arg, param) =>
val origArg = arg match {
case Ident(n) => namedArgs.getOrElse(n, arg)
case _ => arg
}
(param.name, tree2ConstArg(origArg, param.tpe.resultType))
}
if (hasError) ErroneousAnnotation
else if (unmappable) UnmappableAnnotation
else AnnotationInfo(info.atp, Nil, assocs.collect({ case (n, Some(arg)) => (n, arg) })).setOriginal(info.original).setPos(info.pos)
} else
info
}
}
}

def typedMacroAnnotation(cdef: ClassDef) = {
Expand Down
68 changes: 26 additions & 42 deletions test/files/neg/annots-constant-neg.check
Expand Up @@ -10,16 +10,16 @@ Test.scala:25: error: annotation argument needs to be a constant; found: Test.th
Test.scala:27: error: annotation JAnn is missing argument value
@JAnn() def t4 = 0 // err
^
Test.scala:30: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
Test.scala:30: error: arguments to Java annotations have to be supplied as named arguments
@JAnn(0, "") def t7 = 0 // err
^
Test.scala:30: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
Test.scala:30: error: arguments to Java annotations have to be supplied as named arguments
@JAnn(0, "") def t7 = 0 // err
^
Test.scala:30: error: annotation JAnn is missing argument value
@JAnn(0, "") def t7 = 0 // err
^
Test.scala:31: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
Test.scala:31: error: arguments to Java annotations have to be supplied as named arguments
@JAnn(0, a = "") def t8 = 0 // err
^
Test.scala:31: error: annotation JAnn is missing argument value
Expand All @@ -46,24 +46,10 @@ Test.scala:41: error: annotation argument needs to be a constant; found: java.la
Test.scala:45: error: annotation argument needs to be a constant; found: Test.this.nonConst
@Ann(nonConst) def u3 = 0 // err
^
Test.scala:47: error: annotation Ann is missing argument value
Test.scala:47: error: not enough arguments for constructor Ann: (value: Int, a: String, b: Class[_], c: Array[Object]): Ann.
Unspecified value parameter value.
@Ann() def u4 = 0 // err
^
Test.scala:50: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
@Ann(0, "") def u7 = 0 // err
^
Test.scala:50: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
@Ann(0, "") def u7 = 0 // err
^
Test.scala:50: error: annotation Ann is missing argument value
@Ann(0, "") def u7 = 0 // err
^
Test.scala:51: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
@Ann(0, a = "") def u8 = 0 // err
^
Test.scala:51: error: annotation Ann is missing argument value
@Ann(0, a = "") def u8 = 0 // err
^
Test.scala:54: error: annotation argument cannot be null
@Ann(value = 0, a = null) def u10 = 0 // err
^
Expand All @@ -76,37 +62,35 @@ Test.scala:56: error: Array constants have to be specified using the `Array(...)
Test.scala:59: error: annotation argument needs to be a constant; found: java.lang.Integer.TYPE
@Ann(value = 0, b = java.lang.Integer.TYPE) def u16 = 0 // err
^
Test.scala:62: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
@Ann1(0) def v2 = 0 // err
^
Test.scala:63: error: unknown annotation argument name: value
@Ann1(value = 0) def v3 = 0 // err
^
Test.scala:64: error: unknown annotation argument name: x
Test.scala:64: error: multiple constructors for Ann1 with alternatives:
(s: String)Ann1 <and>
(value: Int)Ann1
cannot be invoked with (x: String)
@Ann1(x = "") def v4 = 0 // err
^
Test.scala:66: error: multiple argument lists on Java annotation or subclass of ConstantAnnotation
^
Test.scala:66: error: Ann1 does not take parameters
@Ann1(0)(0) def v6 = 0 // err
^
Test.scala:67: error: annotation Ann2 is missing argument x
Test.scala:67: error: not enough arguments for constructor Ann2: (x: Int)(y: Int): Ann2.
Unspecified value parameter x.
@Ann2 def v7 = 0 // err
^
Test.scala:69: error: multiple argument lists on Java annotation or subclass of ConstantAnnotation
@Ann2(x = 0)(y = 0) def v9 = 0 // err
Test.scala:68: error: missing argument list for constructor Ann2 in class Ann2
@Ann2(x = 0) def v8 = 0 // err
^
Test.scala:71: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
Test.scala:71: error: no arguments allowed for nullary constructor Ann3: (): Ann3
@Ann3(0) def v11 = 0 // err
^
Test.scala:73: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
@Ann4(0, 1) def v13 = 0 // err
^
Test.scala:73: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
@Ann4(0, 1) def v13 = 0 // err
^
Test.scala:73: error: annotation Ann4 is missing argument value
@Ann4(0, 1) def v13 = 0 // err
Test.scala:72: error: not enough arguments for constructor Ann4: (x: Int, value: Int): Ann4.
Unspecified value parameter value.
@Ann4(0) def v12 = 0
^
Test.scala:78: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
Test.scala:78: error: no arguments allowed for nullary constructor Ann5: (): Ann5
@Ann5(0) def v18 = 0 // err
^
37 errors
Test.scala:69: warning: Implementation limitation: multiple argument lists on annotations are
currently not supported; ignoring arguments List(0)
@Ann2(x = 0)(y = 0) def v9 = 0 // warn
^
1 warning
28 errors
23 changes: 15 additions & 8 deletions test/files/neg/annots-constant-neg/Test.scala
Expand Up @@ -15,6 +15,8 @@ class Ann2(x: Int)(y: Int) extends ConstantAnnotation // err
class Ann3 extends ConstantAnnotation
class Ann4(x: Int = 0, value: Int) extends ConstantAnnotation
class Ann5() extends ConstantAnnotation
class Ann6(x: Int) extends ConstantAnnotation // scala/bug#11724
class Ann7[T](x: T) extends annotation.ConstantAnnotation // scala/bug#11724

object Test {
final val const = 1
Expand Down Expand Up @@ -47,8 +49,8 @@ object Test {
@Ann() def u4 = 0 // err
@Ann(value = 0) def u5 = 0
@Ann(value = 0, a = "") def u6 = 0
@Ann(0, "") def u7 = 0 // err
@Ann(0, a = "") def u8 = 0 // err
@Ann(0, "") def u7 = 0
@Ann(0, a = "") def u8 = 0

@Ann(value = 0, a = "moin", b = classOf[Object], c = Array("")) def u9 = 0
@Ann(value = 0, a = null) def u10 = 0 // err
Expand All @@ -59,21 +61,26 @@ object Test {
@Ann(value = 0, b = java.lang.Integer.TYPE) def u16 = 0 // err

@Ann1() def v1 = 0
@Ann1(0) def v2 = 0 // err
@Ann1(value = 0) def v3 = 0 // err
@Ann1(0) def v2 = 0
@Ann1(value = 0) def v3 = 0
@Ann1(x = "") def v4 = 0 // err
@Ann1 def v5 = 0 // err
@Ann1 def v5 = 0
@Ann1(0)(0) def v6 = 0 // err
@Ann2 def v7 = 0 // err
@Ann2(x = 0) def v8 = 0
@Ann2(x = 0)(y = 0) def v9 = 0 // err
@Ann2(x = 0) def v8 = 0 // err
@Ann2(x = 0)(y = 0) def v9 = 0 // warn
@Ann3 def v10 = 0
@Ann3(0) def v11 = 0 // err
@Ann4(0) def v12 = 0
@Ann4(0, 1) def v13 = 0 // err
@Ann4(0, 1) def v13 = 0
@Ann4(x = 0, value = 1) def v14 = 0
@Ann4(value = 1, x = 0) def v15 = 0
@Ann5 def v16 = 0
@Ann5() def v17 = 0
@Ann5(0) def v18 = 0 // err

@Ann6(1) def w1 = 0

@Ann7(1) def x1 = 0
@Ann7(Array("")) def x2 = 0
}
5 changes: 4 additions & 1 deletion test/files/neg/nested-annotation.check
@@ -1,4 +1,7 @@
nested-annotation.scala:8: error: nested classfile annotations must be defined in java; found: inline
@ComplexAnnotation(new inline) def bippy(): Int = 1
^
1 error
nested-annotation.scala:11: error: no arguments allowed for nullary constructor SuppressWarnings: (): SuppressWarnings
@ComplexAnnotation(new SuppressWarnings(Array("blup"))) def huppy(): Int = 2
^
2 errors
2 changes: 1 addition & 1 deletion test/files/neg/t5182.check
@@ -1,7 +1,7 @@
t5182.scala:4: error: unknown annotation argument name: qwe
@java.lang.Deprecated(qwe = "wer") def ok(q:Int) = 1
^
t5182.scala:5: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments
t5182.scala:5: error: arguments to Java annotations have to be supplied as named arguments
@java.lang.Deprecated("wer") def whereAmI(q:Int) = 1
^
2 errors
7 changes: 0 additions & 7 deletions test/files/neg/t6082.check

This file was deleted.

File renamed without changes.
2 changes: 1 addition & 1 deletion test/files/run/reflection-scala-annotations.check
@@ -1,2 +1,2 @@
new sann(1, scala.`package`.List.apply[Int](1, 2))
new jann(y = Array(1, 2), x = 2)
new jann(x = 2, y = Array(1, 2))

0 comments on commit ebb5ab1

Please sign in to comment.