From 0e4ca7e09ae2f24f36cd52115b4464f4efd4e2e1 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 9 Dec 2020 16:48:36 +0100 Subject: [PATCH 1/3] Typecheck ConstantAnnotations normally, not like Java annotations 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. --- .../tools/nsc/typechecker/ContextErrors.scala | 4 +- .../scala/tools/nsc/typechecker/Typers.scala | 68 +++++++++++++------ test/files/neg/annots-constant-neg.check | 68 +++++++------------ test/files/neg/annots-constant-neg/Test.scala | 16 ++--- test/files/neg/nested-annotation.check | 5 +- test/files/neg/t5182.check | 2 +- test/files/neg/t6082.check | 7 -- test/files/{neg => pos}/t6082.scala | 0 .../run/reflection-scala-annotations.check | 2 +- 9 files changed, 89 insertions(+), 83 deletions(-) delete mode 100644 test/files/neg/t6082.check rename test/files/{neg => pos}/t6082.scala (100%) diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 4a460cc8b7ce..57888bf6d3cc 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -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) @@ -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) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index b5538dfb2441..d279bb8ef02b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -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 @@ -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 { @@ -4024,15 +4029,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper 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 { @@ -4043,8 +4043,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) @@ -4053,7 +4052,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) } @@ -4086,10 +4085,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) => @@ -4124,12 +4124,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) = { diff --git a/test/files/neg/annots-constant-neg.check b/test/files/neg/annots-constant-neg.check index 371079dfe85e..6d7e198f4653 100644 --- a/test/files/neg/annots-constant-neg.check +++ b/test/files/neg/annots-constant-neg.check @@ -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 @@ -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 ^ @@ -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 + (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 diff --git a/test/files/neg/annots-constant-neg/Test.scala b/test/files/neg/annots-constant-neg/Test.scala index 3d1456fd020e..8c134854f477 100644 --- a/test/files/neg/annots-constant-neg/Test.scala +++ b/test/files/neg/annots-constant-neg/Test.scala @@ -47,8 +47,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 @@ -59,18 +59,18 @@ 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 diff --git a/test/files/neg/nested-annotation.check b/test/files/neg/nested-annotation.check index 836e85c54904..f296b36d1d25 100644 --- a/test/files/neg/nested-annotation.check +++ b/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 diff --git a/test/files/neg/t5182.check b/test/files/neg/t5182.check index 914228b94b1d..528fda36eaf9 100644 --- a/test/files/neg/t5182.check +++ b/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 diff --git a/test/files/neg/t6082.check b/test/files/neg/t6082.check deleted file mode 100644 index 5758bc9e8477..000000000000 --- a/test/files/neg/t6082.check +++ /dev/null @@ -1,7 +0,0 @@ -t6082.scala:2: error: arguments to Java annotations or subclasses of ConstantAnnotation have to be supplied as named arguments -@annot("") class C - ^ -t6082.scala:2: error: annotation annot is missing argument notValue -@annot("") class C - ^ -2 errors diff --git a/test/files/neg/t6082.scala b/test/files/pos/t6082.scala similarity index 100% rename from test/files/neg/t6082.scala rename to test/files/pos/t6082.scala diff --git a/test/files/run/reflection-scala-annotations.check b/test/files/run/reflection-scala-annotations.check index 22b8711aa15c..e4fdb89b757e 100644 --- a/test/files/run/reflection-scala-annotations.check +++ b/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)) From e346b8cd588b447db9276ac4a464dd8634f18f10 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 11 Dec 2020 21:11:39 +0100 Subject: [PATCH 2/3] Don't re-run typer on already typed ConstantAnnotation When extracting constants from an already typed ConstantAnnotation, don't call the typer again. --- .../scala/tools/nsc/typechecker/Typers.scala | 44 +++++++-------- test/files/neg/annots-constant-neg.check | 54 +++++++++---------- test/files/neg/annots-constant-neg/Test.scala | 7 +++ test/files/neg/nested-annotation.check | 2 +- 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d279bb8ef02b..8044749ae185 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -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 = 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 { @@ -3960,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 @@ -3974,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 treeInfo.Applied(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 _ => @@ -4006,23 +4025,6 @@ 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 diff --git a/test/files/neg/annots-constant-neg.check b/test/files/neg/annots-constant-neg.check index 6d7e198f4653..5d45b23d5b30 100644 --- a/test/files/neg/annots-constant-neg.check +++ b/test/files/neg/annots-constant-neg.check @@ -4,91 +4,91 @@ 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 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 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 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:47: error: annotation argument needs to be a constant; found: Test.this.nonConst @Ann(nonConst) def u3 = 0 // err ^ -Test.scala:47: error: not enough arguments for constructor Ann: (value: Int, a: String, b: Class[_], c: Array[Object]): Ann. +Test.scala:49: 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:54: error: annotation argument cannot be null +Test.scala:56: 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:57: 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:58: 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:61: 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:64: error: multiple constructors for Ann1 with alternatives: +Test.scala:66: error: multiple constructors for Ann1 with alternatives: (s: String)Ann1 (value: Int)Ann1 cannot be invoked with (x: String) @Ann1(x = "") def v4 = 0 // err ^ -Test.scala:66: error: Ann1 does not take parameters +Test.scala:68: error: Ann1 does not take parameters @Ann1(0)(0) def v6 = 0 // err ^ -Test.scala:67: error: not enough arguments for constructor Ann2: (x: Int)(y: Int): Ann2. +Test.scala:69: error: not enough arguments for constructor Ann2: (x: Int)(y: Int): Ann2. Unspecified value parameter x. @Ann2 def v7 = 0 // err ^ -Test.scala:68: error: missing argument list for constructor Ann2 in class Ann2 +Test.scala:70: error: missing argument list for constructor Ann2 in class Ann2 @Ann2(x = 0) def v8 = 0 // err ^ -Test.scala:71: error: no arguments allowed for nullary constructor Ann3: (): Ann3 +Test.scala:73: error: no arguments allowed for nullary constructor Ann3: (): Ann3 @Ann3(0) def v11 = 0 // err ^ -Test.scala:72: error: not enough arguments for constructor Ann4: (x: Int, value: Int): Ann4. +Test.scala:74: 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: no arguments allowed for nullary constructor Ann5: (): Ann5 +Test.scala:80: error: no arguments allowed for nullary constructor Ann5: (): Ann5 @Ann5(0) def v18 = 0 // err ^ -Test.scala:69: warning: Implementation limitation: multiple argument lists on annotations are +Test.scala:71: 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 ^ diff --git a/test/files/neg/annots-constant-neg/Test.scala b/test/files/neg/annots-constant-neg/Test.scala index 8c134854f477..7ecbdd34152f 100644 --- a/test/files/neg/annots-constant-neg/Test.scala +++ b/test/files/neg/annots-constant-neg/Test.scala @@ -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 @@ -76,4 +78,9 @@ object Test { @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 } diff --git a/test/files/neg/nested-annotation.check b/test/files/neg/nested-annotation.check index f296b36d1d25..21f30ae5517e 100644 --- a/test/files/neg/nested-annotation.check +++ b/test/files/neg/nested-annotation.check @@ -1,4 +1,4 @@ -nested-annotation.scala:8: error: nested classfile annotations must be defined in java; found: inline +nested-annotation.scala:8: error: annotation argument needs to be a constant; found: new scala.inline() @ComplexAnnotation(new inline) def bippy(): Int = 1 ^ nested-annotation.scala:11: error: no arguments allowed for nullary constructor SuppressWarnings: (): SuppressWarnings From a6bf0128466f827e3521dd5a5229a6f4abb1eb18 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 11 Dec 2020 21:12:24 +0100 Subject: [PATCH 3/3] Better error for Java annotation nested in a Scala ConstantAnnotation Java supports nested annotations as constant annotation arguments, but Scala's ConstantAnnotation doesn't. Better error message for this case. --- .../scala/tools/nsc/typechecker/Typers.scala | 4 +- test/files/neg/annots-constant-neg.check | 44 ++++++++++++------- test/files/neg/annots-constant-neg/Test.scala | 10 +++++ test/files/neg/nested-annotation.check | 7 --- test/files/neg/nested-annotation.scala | 12 ----- 5 files changed, 41 insertions(+), 36 deletions(-) delete mode 100644 test/files/neg/nested-annotation.check delete mode 100644 test/files/neg/nested-annotation.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8044749ae185..93959f6cb375 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -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]() @@ -3921,7 +3921,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // begin typedAnnotation val treeInfo.Applied(fun0, _, argss) = ann if (fun0.isErroneous) return finish(ErroneousAnnotation) - val typedFun = typed(fun0, mode.forFunMode) + val typedFun = context.withinAnnotation(typed(fun0, mode.forFunMode)) if (typedFun.isErroneous) return finish(ErroneousAnnotation) val Select(New(annTpt), _) = typedFun: @unchecked diff --git a/test/files/neg/annots-constant-neg.check b/test/files/neg/annots-constant-neg.check index 5d45b23d5b30..800e06c70489 100644 --- a/test/files/neg/annots-constant-neg.check +++ b/test/files/neg/annots-constant-neg.check @@ -43,54 +43,68 @@ Test.scala:40: error: annotation argument cannot be null 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:47: 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:49: error: not enough arguments for constructor Ann: (value: Int, a: String, b: Class[_], c: Array[Object]): Ann. +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:56: 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:57: 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:58: 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:61: 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:66: error: multiple constructors for Ann1 with alternatives: +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 (value: Int)Ann1 cannot be invoked with (x: String) @Ann1(x = "") def v4 = 0 // err ^ -Test.scala:68: error: Ann1 does not take parameters +Test.scala:78: error: Ann1 does not take parameters @Ann1(0)(0) def v6 = 0 // err ^ -Test.scala:69: error: not enough arguments for constructor Ann2: (x: Int)(y: Int): Ann2. +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:70: error: missing argument list for constructor Ann2 in class Ann2 +Test.scala:80: error: missing argument list for constructor Ann2 in class Ann2 @Ann2(x = 0) def v8 = 0 // err ^ -Test.scala:73: error: no arguments allowed for nullary constructor Ann3: (): Ann3 +Test.scala:83: error: no arguments allowed for nullary constructor Ann3: (): Ann3 @Ann3(0) def v11 = 0 // err ^ -Test.scala:74: error: not enough arguments for constructor Ann4: (x: Int, value: Int): Ann4. +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:80: error: no arguments allowed for nullary constructor Ann5: (): Ann5 +Test.scala:90: error: no arguments allowed for nullary constructor Ann5: (): Ann5 @Ann5(0) def v18 = 0 // err ^ -Test.scala:71: warning: Implementation limitation: multiple argument lists on annotations are +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 -28 errors +32 errors diff --git a/test/files/neg/annots-constant-neg/Test.scala b/test/files/neg/annots-constant-neg/Test.scala index 7ecbdd34152f..fa2779b94ca4 100644 --- a/test/files/neg/annots-constant-neg/Test.scala +++ b/test/files/neg/annots-constant-neg/Test.scala @@ -42,6 +42,11 @@ object Test { @JAnn(value = 0, b = classOf[Int]) def t15 = 0 @JAnn(value = 0, b = java.lang.Integer.TYPE) def t16 = 0 // err + // nested annotation is ok + @JAnn(value = 0, c = Array(new SuppressWarnings(value = Array("")))) def t17 = 0 + // but the nested annotation needs to be itself a Java annotation + @JAnn(value = 0, c = Array(new inline)) def t18 = 0 // err + @Ann(1) def u1 = 0 @Ann(const) def u2 = 0 @Ann(nonConst) def u3 = 0 // err @@ -60,6 +65,11 @@ object Test { @Ann(value = 0, b = classOf[Int]) def u15 = 0 @Ann(value = 0, b = java.lang.Integer.TYPE) def u16 = 0 // err + // nested annotations are only allowed for Java annotations, not for Scala ConstantAnnotations + @Ann(value = 0, c = Array(new SuppressWarnings(value = Array("")))) def u17 = 0 // err + // the outer and the nested annotation need to be Java annotations + @Ann(value = 0, c = Array(new inline)) def u18 = 0 // err + @Ann1() def v1 = 0 @Ann1(0) def v2 = 0 @Ann1(value = 0) def v3 = 0 diff --git a/test/files/neg/nested-annotation.check b/test/files/neg/nested-annotation.check deleted file mode 100644 index 21f30ae5517e..000000000000 --- a/test/files/neg/nested-annotation.check +++ /dev/null @@ -1,7 +0,0 @@ -nested-annotation.scala:8: error: annotation argument needs to be a constant; found: new scala.inline() - @ComplexAnnotation(new inline) def bippy(): Int = 1 - ^ -nested-annotation.scala:11: error: no arguments allowed for nullary constructor SuppressWarnings: (): SuppressWarnings - @ComplexAnnotation(new SuppressWarnings(Array("blup"))) def huppy(): Int = 2 - ^ -2 errors diff --git a/test/files/neg/nested-annotation.scala b/test/files/neg/nested-annotation.scala deleted file mode 100644 index 71bcb7cb064f..000000000000 --- a/test/files/neg/nested-annotation.scala +++ /dev/null @@ -1,12 +0,0 @@ -import annotation._ - -class ComplexAnnotation(val value: Any) extends ConstantAnnotation - -class A { - // It's hard to induce this error because @ComplexAnnotation(@inline) is a parse - // error so it never gets out of the parser, but: - @ComplexAnnotation(new inline) def bippy(): Int = 1 - - // No error here, as `SuppressWarnings` is defined in Java - @ComplexAnnotation(new SuppressWarnings(Array("blup"))) def huppy(): Int = 2 -}