From ebb5ab18ab7c399aba72eea4939fcfa74e2fc4c0 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 9 Dec 2020 16:48:36 +0100 Subject: [PATCH] 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 | 66 ++++++++++++------ test/files/neg/annots-constant-neg.check | 68 +++++++------------ test/files/neg/annots-constant-neg/Test.scala | 23 ++++--- 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, 96 insertions(+), 81 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..23401ce6ff2a 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 { @@ -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 { @@ -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) @@ -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) } @@ -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) => @@ -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) = { 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..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 @@ -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 @@ -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 } 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))