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

Lift artificial restrictions on ConstantAnnotations #9379

Merged
merged 3 commits into from Dec 17, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
112 changes: 70 additions & 42 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -3858,7 +3858,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* Convert an annotation constructor call into an AnnotationInfo.
*/
@nowarn("cat=lint-nonlocal-return")
def typedAnnotation(ann: Tree, annotee: Option[Tree], mode: Mode = EXPRmode): AnnotationInfo = context.withinAnnotation {
def typedAnnotation(ann: Tree, annotee: Option[Tree], mode: Mode = EXPRmode): AnnotationInfo = {
var hasError: Boolean = false
var unmappable: Boolean = false
val pending = ListBuffer[AbsTypeError]()
Expand Down Expand Up @@ -3918,13 +3918,30 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
ErroneousAnnotation
}

// begin typedAnnotation
val treeInfo.Applied(fun0, _, argss) = ann
if (fun0.isErroneous) return finish(ErroneousAnnotation)
val typedFun = context.withinAnnotation(typed(fun0, mode.forFunMode))
if (typedFun.isErroneous) return finish(ErroneousAnnotation)

val Select(New(annTpt), _) = typedFun: @unchecked
val annType = annTpt.tpe // for a polymorphic annotation class, this type will have unbound type params (see context.undetparams)
val annTypeSym = annType.typeSymbol
val isJava = annTypeSym.isJavaDefined

val isAnnotation = annTypeSym.isJavaAnnotation || annType <:< AnnotationClass.tpe
if (!isAnnotation) {
reportAnnotationError(DoesNotExtendAnnotation(typedFun, annTypeSym))
return finish(ErroneousAnnotation)
}

/* Calling constfold right here is necessary because some trees (negated
* floats and literals in particular) are not yet folded.
*/
def tryConst(tr: Tree, pt: Type): Option[LiteralAnnotArg] = {
// The typed tree may be relevantly different than the tree `tr`,
// e.g. it may have encountered an implicit conversion.
val ttree = typed(constfold(tr, context.owner), pt)
val ttree = if (isJava) typed(constfold(tr, context.owner), pt) else tr
val const: Constant = ttree match {
case l @ Literal(c) if !l.isErroneous => c
case tree => tree.tpe match {
Expand All @@ -3933,9 +3950,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 All @@ -3955,7 +3977,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
case Apply(Select(New(_), nme.CONSTRUCTOR), _) if pt.typeSymbol == ArrayClass =>
reportAnnotationError(ArrayConstantsError(tree)); None

case ann @ Apply(Select(New(tpt), nme.CONSTRUCTOR), _) =>
case ann @ Apply(Select(New(tpt), nme.CONSTRUCTOR), _) if isJava =>
val annInfo = typedAnnotation(ann, None, mode)
val annType = annInfo.atp

Expand All @@ -3969,10 +3991,12 @@ 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) =>
val typedFun = typed(fun, mode.forFunMode)
case treeInfo.Applied(fun, targs, args :: _) =>
val typedFun = if (isJava) typed(fun, mode.forFunMode) else fun
if (typedFun.symbol.owner == ArrayModule.moduleClass && typedFun.symbol.name == nme.apply)
pt match {
case _ if !isJava =>
trees2ConstArg(args, targs.headOption.map(_.tpe).getOrElse(WildcardType))
case TypeRef(_, ArrayClass, targ :: _) =>
trees2ConstArg(args, targ)
case _ =>
Expand Down Expand Up @@ -4001,38 +4025,16 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
.map(args => ArrayAnnotArg(args.toArray))
}

// begin typedAnnotation
val treeInfo.Applied(fun0, _, argss) = ann
if (fun0.isErroneous) return finish(ErroneousAnnotation)
val typedFun = typed(fun0, mode.forFunMode)
if (typedFun.isErroneous) return finish(ErroneousAnnotation)

val Select(New(annTpt), _) = typedFun: @unchecked
val annType = annTpt.tpe // for a polymorphic annotation class, this type will have unbound type params (see context.undetparams)
val annTypeSym = annType.typeSymbol
val isJava = annTypeSym.isJavaDefined

val isAnnotation = annTypeSym.isJavaAnnotation || annType <:< AnnotationClass.tpe
if (!isAnnotation) {
reportAnnotationError(DoesNotExtendAnnotation(typedFun, annTypeSym))
return finish(ErroneousAnnotation)
}

@inline def constantly = {
// Arguments of Java annotations and ConstantAnnotations are checked to be constants and
// stored in the `assocs` field of the resulting AnnotationInfo
if (argss.lengthIs > 1) {
reportAnnotationError(MultipleArgumentListForAnnotationError(ann))
} 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
112 changes: 55 additions & 57 deletions test/files/neg/annots-constant-neg.check
Expand Up @@ -4,109 +4,107 @@ Test.scala:12: error: class Ann1 cannot have auxiliary constructors because it e
Test.scala:14: error: class Ann2 needs to have exactly one argument list because it extends ConstantAnnotation
class Ann2(x: Int)(y: Int) extends ConstantAnnotation // err
^
Test.scala:25: error: annotation argument needs to be a constant; found: Test.this.nonConst
Test.scala:27: error: annotation argument needs to be a constant; found: Test.this.nonConst
@JAnn(nonConst) def t3 = 0 // err
^
Test.scala:27: error: annotation JAnn is missing argument value
Test.scala:29: 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:32: 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:32: 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
Test.scala:32: 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:33: 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
Test.scala:33: error: annotation JAnn is missing argument value
@JAnn(0, a = "") def t8 = 0 // err
^
Test.scala:34: error: annotation argument cannot be null
Test.scala:36: error: annotation argument cannot be null
@JAnn(value = 0, a = null) def t10 = 0 // err
^
Test.scala:35: error: annotation argument needs to be a constant; found: Test.this.getClass()
Test.scala:37: error: annotation argument needs to be a constant; found: Test.this.getClass()
@JAnn(value = 0, b = getClass) def t11 = 0 // err
^
Test.scala:36: error: Array constants have to be specified using the `Array(...)` factory method
Test.scala:38: error: Array constants have to be specified using the `Array(...)` factory method
@JAnn(value = 0, c = new Array(1)) def t12 = 0 // err
^
Test.scala:37: error: annotation argument cannot be null
Test.scala:39: error: annotation argument cannot be null
@JAnn(value = 0, d = null) def t13 = 0 // err
^
Test.scala:38: error: annotation argument cannot be null
Test.scala:40: error: annotation argument cannot be null
@JAnn(value = 0, d = null) def t14 = 0 // err
^
Test.scala:41: error: annotation argument needs to be a constant; found: java.lang.Integer.TYPE
Test.scala:43: error: annotation argument needs to be a constant; found: java.lang.Integer.TYPE
@JAnn(value = 0, b = java.lang.Integer.TYPE) def t16 = 0 // err
^
Test.scala:45: error: annotation argument needs to be a constant; found: Test.this.nonConst
Test.scala:48: error: nested classfile annotations must be defined in java; found: inline
@JAnn(value = 0, c = Array(new inline)) def t18 = 0 // err
^
Test.scala:52: 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:54: 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
Test.scala:61: error: annotation argument cannot be null
@Ann(value = 0, a = null) def u10 = 0 // err
^
Test.scala:55: error: annotation argument needs to be a constant; found: Test.this.getClass()
Test.scala:62: error: annotation argument needs to be a constant; found: Test.this.getClass()
@Ann(value = 0, b = getClass) def u11 = 0 // err
^
Test.scala:56: error: Array constants have to be specified using the `Array(...)` factory method
Test.scala:63: error: Array constants have to be specified using the `Array(...)` factory method
@Ann(value = 0, c = new Array(1)) def u12 = 0 // err
^
Test.scala:59: error: annotation argument needs to be a constant; found: java.lang.Integer.TYPE
Test.scala:66: 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:69: error: Java annotation SuppressWarnings is abstract; cannot be instantiated
Error occurred in an application involving default arguments.
@Ann(value = 0, c = Array(new SuppressWarnings(value = Array("")))) def u17 = 0 // err
^
Test.scala:69: error: not found: value value
Error occurred in an application involving default arguments.
@Ann(value = 0, c = Array(new SuppressWarnings(value = Array("")))) def u17 = 0 // err
^
Test.scala:71: error: annotation argument needs to be a constant; found: new scala.inline()
@Ann(value = 0, c = Array(new inline)) def u18 = 0 // err
^
Test.scala:76: 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:78: 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:79: 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:80: 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:83: 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:84: 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:90: error: no arguments allowed for nullary constructor Ann5: (): Ann5
@Ann5(0) def v18 = 0 // err
^
37 errors
Test.scala:81: 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
32 errors