Skip to content

Commit

Permalink
Merge pull request #9379 from lrytz/constAnn
Browse files Browse the repository at this point in the history
  • Loading branch information
lrytz committed Dec 17, 2020
2 parents 00599ca + a6bf012 commit 0772f76
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 134 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
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

0 comments on commit 0772f76

Please sign in to comment.