From dbcf30f34f198b797501853217b7947c55a85e89 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 4 Nov 2024 17:50:40 +0100 Subject: [PATCH] Allow any applications in annotations --- .../src/dotty/tools/dotc/typer/Checking.scala | 51 +++------- tests/neg/annot-invalid.check | 92 +++++++++---------- tests/neg/annot-invalid.scala | 13 +-- tests/neg/nowarn.check | 78 +++++++++------- tests/neg/nowarn.scala | 3 + tests/neg/serialversionuid-not-const.scala | 5 +- tests/pos/annot-valid.scala | 10 +- .../{neg/t1942.scala => pos/t1942/A_1.scala} | 2 +- tests/pos/t1942/Test_2.scala | 3 + tests/{neg => pos}/t5892.scala | 2 +- tests/run/i12656.scala | 2 +- tests/warn/i15503i.scala | 6 +- 12 files changed, 128 insertions(+), 139 deletions(-) rename tests/{neg/t1942.scala => pos/t1942/A_1.scala} (69%) create mode 100644 tests/pos/t1942/Test_2.scala rename tests/{neg => pos}/t5892.scala (60%) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 499e39f5eaaf..a6e7e01ea87d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1433,47 +1433,26 @@ trait Checking { tree private def checkAnnotArg(tree: Tree)(using Context): Unit = - def isTupleModule(sym: Symbol): Boolean = - ctx.definitions.isTupleClass(sym.companionClass) - - def isFunctionAllowed(t: Tree): Boolean = + def valid(t: Tree): Boolean = t match - case Select(qual, nme.apply) => - qual.symbol == defn.ArrayModule - || qual.symbol == defn.ClassTagModule // class tags are used as arguments to Array.apply - || qual.symbol == defn.SymbolModule // used in Akka - || qual.symbol == defn.JSSymbolModule // used in Scala.js - || isTupleModule(qual.symbol) - case Select(New(clazz), nme.CONSTRUCTOR) => clazz.symbol.isAnnotation - case Apply(fun, _) => isFunctionAllowed(fun) - case TypeApply(fun, _) => isFunctionAllowed(fun) + case _ if t.tpe.isEffectivelySingleton => true + case Literal(_) => true + // `_` is used as placeholder for unspecified arguments of Java + // annotations. Example: tests/run/java-ann-super-class + case Ident(nme.WILDCARD) => true + case Apply(fun, args) => valid(fun) && args.forall(valid) + case TypeApply(fun, args) => valid(fun) + case SeqLiteral(elems, _) => elems.forall(valid) + case Typed(expr, _) => valid(expr) + case NamedArg(_, arg) => valid(arg) + case Splice(_) => true + case Hole(_, _, _, _) => true case _ => false - - def valid(t: Tree): Boolean = - t.tpe.isEffectivelySingleton - || ( - t match - case Literal(_) => true - // `_` is used as placeholder for unspecified arguments of Java - // annotations. Example: tests/run/java-ann-super-class - case Ident(nme.WILDCARD) => true - case Apply(fun, args) => isFunctionAllowed(fun) && args.forall(valid) - case TypeApply(fun, args) => isFunctionAllowed(fun) - // Support for `x.isInstanceOf[T]`. Probably not needed. - //case TypeApply(meth @ Select(arg, _), _) if meth.symbol == defn.Any_asInstanceOf => valid(arg) - case SeqLiteral(elems, _) => elems.forall(valid) - case Typed(expr, _) => valid(expr) - case NamedArg(_, arg) => valid(arg) - case Splice(_) => true - case Hole(_, _, _, _) => true - case _ => false - ) - if !valid(tree) then report.error( i"""Implementation restriction: not a valid annotation argument. - | Argument: $tree - | Type: ${tree.tpe}""", + |Argument: $tree + |Type: ${tree.tpe}""", tree.srcPos ) diff --git a/tests/neg/annot-invalid.check b/tests/neg/annot-invalid.check index ed2fbfcce423..7014255d39ae 100644 --- a/tests/neg/annot-invalid.check +++ b/tests/neg/annot-invalid.check @@ -1,54 +1,48 @@ --- Error: tests/neg/annot-invalid.scala:7:21 --------------------------------------------------------------------------- -7 | val x1: Int @annot(n + 1) = 0 // error - | ^^^^^ +-- Error: tests/neg/annot-invalid.scala:4:21 --------------------------------------------------------------------------- +4 | val x1: Int @annot(new Object {}) = 0 // error + | ^^^^^^^^^^^^^ | Implementation restriction: not a valid annotation argument. - | Argument: n.+(1) - | Type: Int --- Error: tests/neg/annot-invalid.scala:8:22 --------------------------------------------------------------------------- -8 | val x2: Int @annot(f(2)) = 0 // error - | ^^^^ + | Argument: { + | final class $anon() extends Object() {} + | new $anon():Object + | } + | Type: Object +-- Error: tests/neg/annot-invalid.scala:5:21 --------------------------------------------------------------------------- +5 | val x2: Int @annot({val x = 1}) = 0 // error + | ^^^^^^^^^^^ | Implementation restriction: not a valid annotation argument. - | Argument: f(2) - | Type: Unit --- Error: tests/neg/annot-invalid.scala:9:21 --------------------------------------------------------------------------- -9 | val x3: Int @annot(throw new Error()) = 0 // error - | ^^^^^^^^^^^^^^^^^ + | Argument: { + | val x: Int = 1 + | () + | } + | Type: Unit +-- Error: tests/neg/annot-invalid.scala:6:21 --------------------------------------------------------------------------- +6 | val x4: Int @annot((x: Int) => x) = 0 // error + | ^^^^^^^^^^^^^ | Implementation restriction: not a valid annotation argument. - | Argument: throw new Error() - | Type: Nothing --- Error: tests/neg/annot-invalid.scala:10:21 -------------------------------------------------------------------------- -10 | val x4: Int @annot((x: Int) => x) = 0 // error - | ^^^^^^^^^^^^^ - | Implementation restriction: not a valid annotation argument. - | Argument: { - | def $anonfun(x: Int): Int = x - | closure($anonfun) - | } - | Type: Int => Int --- Error: tests/neg/annot-invalid.scala:12:9 --------------------------------------------------------------------------- -12 | @annot(n + 1) val y1: Int = 0 // error - | ^^^^^ - | Implementation restriction: not a valid annotation argument. - | Argument: n.+(1) - | Type: Int --- Error: tests/neg/annot-invalid.scala:13:10 -------------------------------------------------------------------------- -13 | @annot(f(2)) val y2: Int = 0 // error - | ^^^^ - | Implementation restriction: not a valid annotation argument. - | Argument: f(2) - | Type: Unit --- Error: tests/neg/annot-invalid.scala:14:9 --------------------------------------------------------------------------- -14 | @annot(throw new Error()) val y3: Int = 0 // error - | ^^^^^^^^^^^^^^^^^ - | Implementation restriction: not a valid annotation argument. - | Argument: throw new Error() - | Type: Nothing --- Error: tests/neg/annot-invalid.scala:15:9 --------------------------------------------------------------------------- -15 | @annot((x: Int) => x) val y4: Int = 0 // error + | Argument: (x: Int) => x + | Type: Int => Int +-- Error: tests/neg/annot-invalid.scala:8:9 ---------------------------------------------------------------------------- +8 | @annot(new Object {}) val y1: Int = 0 // error + | ^^^^^^^^^^^^^ + | Implementation restriction: not a valid annotation argument. + | Argument: { + | final class $anon() extends Object() {} + | new $anon():Object + | } + | Type: Object +-- Error: tests/neg/annot-invalid.scala:9:9 ---------------------------------------------------------------------------- +9 | @annot({val x = 1}) val y2: Int = 0 // error + | ^^^^^^^^^^^ + | Implementation restriction: not a valid annotation argument. + | Argument: { + | val x: Int = 1 + | () + | } + | Type: Unit +-- Error: tests/neg/annot-invalid.scala:10:9 --------------------------------------------------------------------------- +10 | @annot((x: Int) => x) val y4: Int = 0 // error | ^^^^^^^^^^^^^ | Implementation restriction: not a valid annotation argument. - | Argument: { - | def $anonfun(x: Int): Int = x - | closure($anonfun) - | } - | Type: Int => Int + | Argument: (x: Int) => x + | Type: Int => Int diff --git a/tests/neg/annot-invalid.scala b/tests/neg/annot-invalid.scala index ae4628d83f76..e17cee100873 100644 --- a/tests/neg/annot-invalid.scala +++ b/tests/neg/annot-invalid.scala @@ -1,17 +1,12 @@ class annot[T](arg: T) extends scala.annotation.Annotation def main = - val n: Int = 0 - def f(x: Any): Unit = () - - val x1: Int @annot(n + 1) = 0 // error - val x2: Int @annot(f(2)) = 0 // error - val x3: Int @annot(throw new Error()) = 0 // error + val x1: Int @annot(new Object {}) = 0 // error + val x2: Int @annot({val x = 1}) = 0 // error val x4: Int @annot((x: Int) => x) = 0 // error - @annot(n + 1) val y1: Int = 0 // error - @annot(f(2)) val y2: Int = 0 // error - @annot(throw new Error()) val y3: Int = 0 // error + @annot(new Object {}) val y1: Int = 0 // error + @annot({val x = 1}) val y2: Int = 0 // error @annot((x: Int) => x) val y4: Int = 0 // error () diff --git a/tests/neg/nowarn.check b/tests/neg/nowarn.check index 078025d0be62..636cabd44d07 100644 --- a/tests/neg/nowarn.check +++ b/tests/neg/nowarn.check @@ -5,22 +5,22 @@ | its body in a block; no exceptions are handled. | | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:22:25 ----------------------------------------------------------------- -22 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) +-- [E002] Syntax Warning: tests/neg/nowarn.scala:25:25 ----------------------------------------------------------------- +25 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) | ^^^^^ | A try without catch or finally is equivalent to putting | its body in a block; no exceptions are handled. | | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:30:26 ----------------------------------------------------------------- -30 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) +-- [E002] Syntax Warning: tests/neg/nowarn.scala:33:26 ----------------------------------------------------------------- +33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) | ^^^^^ | A try without catch or finally is equivalent to putting | its body in a block; no exceptions are handled. | | longer explanation available when compiling with `-explain` --- [E002] Syntax Warning: tests/neg/nowarn.scala:32:28 ----------------------------------------------------------------- -32 |@nowarn("verbose") def t5 = try 1 // warning with details +-- [E002] Syntax Warning: tests/neg/nowarn.scala:35:28 ----------------------------------------------------------------- +35 |@nowarn("verbose") def t5 = try 1 // warning with details | ^^^^^ | A try without catch or finally is equivalent to putting | its body in a block; no exceptions are handled. @@ -40,61 +40,71 @@ Matching filters for @nowarn or -Wconf: | ^^^^^^ | Invalid message filter | unknown filter: wat? --- Warning: tests/neg/nowarn.scala:22:10 ------------------------------------------------------------------------------- -22 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) +-- [E129] Potential Issue Warning: tests/neg/nowarn.scala:18:12 -------------------------------------------------------- +18 |def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/neg/nowarn.scala:17:8 -------------------------------------------------------------------------------- +17 |@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) + | ^^^^^^^^^^^^ + | filter needs to be a compile-time constant string +-- Warning: tests/neg/nowarn.scala:25:10 ------------------------------------------------------------------------------- +25 |@nowarn(o.inl) def t2d = try 1 // two warnings (`inl` is not a compile-time constant) | ^^^^^ | filter needs to be a compile-time constant string --- Deprecation Warning: tests/neg/nowarn.scala:36:10 ------------------------------------------------------------------- -36 |def t6a = f // warning (refchecks, deprecation) +-- Deprecation Warning: tests/neg/nowarn.scala:39:10 ------------------------------------------------------------------- +39 |def t6a = f // warning (refchecks, deprecation) | ^ | method f is deprecated --- Deprecation Warning: tests/neg/nowarn.scala:39:30 ------------------------------------------------------------------- -39 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +-- Deprecation Warning: tests/neg/nowarn.scala:42:30 ------------------------------------------------------------------- +42 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) | ^ | method f is deprecated --- Deprecation Warning: tests/neg/nowarn.scala:46:10 ------------------------------------------------------------------- -46 |def t7c = f // warning (deprecation) +-- Deprecation Warning: tests/neg/nowarn.scala:49:10 ------------------------------------------------------------------- +49 |def t7c = f // warning (deprecation) | ^ | method f is deprecated --- [E092] Pattern Match Unchecked Warning: tests/neg/nowarn.scala:52:7 ------------------------------------------------- -52 | case _: List[Int] => 0 // warning (patmat, unchecked) +-- [E092] Pattern Match Unchecked Warning: tests/neg/nowarn.scala:55:7 ------------------------------------------------- +55 | case _: List[Int] => 0 // warning (patmat, unchecked) | ^ |the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any | | longer explanation available when compiling with `-explain` --- Error: tests/neg/nowarn.scala:30:1 ---------------------------------------------------------------------------------- -30 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) +-- Error: tests/neg/nowarn.scala:33:1 ---------------------------------------------------------------------------------- +33 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id) |^^^^^^^^^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:39:1 ---------------------------------------------------------------------------------- -39 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) +-- Error: tests/neg/nowarn.scala:42:1 ---------------------------------------------------------------------------------- +42 |@nowarn("msg=fish") def t6d = f // error (unused nowarn), warning (deprecation) |^^^^^^^^^^^^^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:47:5 ---------------------------------------------------------------------------------- -47 | : @nowarn("msg=fish") // error (unused nowarn) +-- Error: tests/neg/nowarn.scala:50:5 ---------------------------------------------------------------------------------- +50 | : @nowarn("msg=fish") // error (unused nowarn) | ^^^^^^^^^^^^^^^^^^^ | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:59:0 ---------------------------------------------------------------------------------- -59 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) +-- Error: tests/neg/nowarn.scala:62:0 ---------------------------------------------------------------------------------- +62 |@nowarn def t9a = { 1: @nowarn; 2 } // error (outer @nowarn is unused) |^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:60:27 --------------------------------------------------------------------------------- -60 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) +-- Error: tests/neg/nowarn.scala:63:27 --------------------------------------------------------------------------------- +63 |@nowarn def t9b = { 1: Int @nowarn; 2 } // error (inner @nowarn is unused, it covers the type, not the expression) | ^^^^^^^ | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:65:0 ---------------------------------------------------------------------------------- -65 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) +-- Error: tests/neg/nowarn.scala:68:0 ---------------------------------------------------------------------------------- +68 |@nowarn @ann(f) def t10b = 0 // error (unused nowarn) |^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:66:8 ---------------------------------------------------------------------------------- -66 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent +-- Error: tests/neg/nowarn.scala:69:8 ---------------------------------------------------------------------------------- +69 |@ann(f: @nowarn) def t10c = 0 // error (unused nowarn), should be silent | ^^^^^^^ | @nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:69:0 ---------------------------------------------------------------------------------- -69 |@nowarn class I1a { // error (unused nowarn) +-- Error: tests/neg/nowarn.scala:72:0 ---------------------------------------------------------------------------------- +72 |@nowarn class I1a { // error (unused nowarn) |^^^^^^^ |@nowarn annotation does not suppress any warnings --- Error: tests/neg/nowarn.scala:74:0 ---------------------------------------------------------------------------------- -74 |@nowarn class I1b { // error (unused nowarn) +-- Error: tests/neg/nowarn.scala:77:0 ---------------------------------------------------------------------------------- +77 |@nowarn class I1b { // error (unused nowarn) |^^^^^^^ |@nowarn annotation does not suppress any warnings diff --git a/tests/neg/nowarn.scala b/tests/neg/nowarn.scala index 667902c56a80..5b18ab5ccc51 100644 --- a/tests/neg/nowarn.scala +++ b/tests/neg/nowarn.scala @@ -14,6 +14,9 @@ def t1a = try 1 // warning (parser) @nowarn("wat?") // warning (typer, invalid filter) def t2 = { 1; 2 } // warning (the invalid nowarn doesn't silence anything) +@nowarn(t1a.toString) // warning (typer, argument not a compile-time constant) +def t2a = { 1; 2 } // warning (invalid nowarn doesn't silence) + object o: final val const = "msg=try" inline def inl = "msg=try" diff --git a/tests/neg/serialversionuid-not-const.scala b/tests/neg/serialversionuid-not-const.scala index 8e47b5df3f42..88e87221d21f 100644 --- a/tests/neg/serialversionuid-not-const.scala +++ b/tests/neg/serialversionuid-not-const.scala @@ -1,9 +1,6 @@ @SerialVersionUID(13l.toLong) class C1 extends Serializable // OK because toLong is constant-folded @SerialVersionUID(13l) class C2 extends Serializable // OK - -//@SerialVersionUID(13.asInstanceOf[Long]) class C3 extends Serializable -//now catched in typer already: not a valid annotation argument - +@SerialVersionUID(13.asInstanceOf[Long]) class C3 extends Serializable // error @SerialVersionUID(Test.bippy) class C4 extends Serializable // error object Test { diff --git a/tests/pos/annot-valid.scala b/tests/pos/annot-valid.scala index 1febfeb00ac7..2cd7264f2242 100644 --- a/tests/pos/annot-valid.scala +++ b/tests/pos/annot-valid.scala @@ -1,7 +1,7 @@ class annot[T](arg: T) extends scala.annotation.Annotation def main = - val n: Int = ??? + val n: Int = 0 def f(x: Any): Unit = () val x1: Int @annot(42) = 0 @@ -14,6 +14,10 @@ def main = val x8: Int @annot(((1,2),3)) = 0 val x9: Int @annot(((1,2),(3,4))) = 0 val x10: Int @annot(Symbol("hello")) = 0 + val x11: Int @annot(n + 1) = 0 + val x12: Int @annot(f(2)) = 0 + val x13: Int @annot(throw new Error()) = 0 + val x14: Int @annot(42: Double) = 0 @annot(42) val y1: Int = 0 @annot("hello") val y2: Int = 0 @@ -25,5 +29,9 @@ def main = @annot(((1,2),3)) val y8: Int = 0 @annot(((1,2),(3,4))) val y9: Int = 0 @annot(Symbol("hello")) val y10: Int = 0 + @annot(n + 1) val y11: Int = 0 + @annot(f(2)) val y12: Int = 0 + @annot(throw new Error()) val y13: Int = 0 + @annot(42: Double) val y14: Int = 0 () diff --git a/tests/neg/t1942.scala b/tests/pos/t1942/A_1.scala similarity index 69% rename from tests/neg/t1942.scala rename to tests/pos/t1942/A_1.scala index 942c12b51cc7..4915b54a6403 100644 --- a/tests/neg/t1942.scala +++ b/tests/pos/t1942/A_1.scala @@ -7,5 +7,5 @@ class ann(x: Int) extends annotation.StaticAnnotation class t { val a = new A - @ann(a.foo(1)) def bar = 1 // error: not a valid annotation + @ann(a.foo(1)) def bar = 1 } diff --git a/tests/pos/t1942/Test_2.scala b/tests/pos/t1942/Test_2.scala new file mode 100644 index 000000000000..6c045bbce53e --- /dev/null +++ b/tests/pos/t1942/Test_2.scala @@ -0,0 +1,3 @@ +class Test { + println(new t) +} diff --git a/tests/neg/t5892.scala b/tests/pos/t5892.scala similarity index 60% rename from tests/neg/t5892.scala rename to tests/pos/t5892.scala index 383c03f85e82..ac383f8c1848 100644 --- a/tests/neg/t5892.scala +++ b/tests/pos/t5892.scala @@ -1,5 +1,5 @@ class foo(a: String) extends annotation.StaticAnnotation object o { implicit def i2s(i: Int): String = "" - @foo(1: String) def blerg: Unit = { } // error: not a valid annotation + @foo(1: String) def blerg: Unit = { } } diff --git a/tests/run/i12656.scala b/tests/run/i12656.scala index 95a869061a5c..ee22988ac771 100644 --- a/tests/run/i12656.scala +++ b/tests/run/i12656.scala @@ -9,7 +9,7 @@ transparent inline def expectTypeCheck( inline code: String, ) : Boolean = compiletime.testing.typeChecks(code) -@main def Test: Unit = +@main def Test = assert(!expectTypeCheck("""compiletime.error("some error")""")) assert(expectTypeCheck("""1 + 1""")) expectCompileError("""compiletime.error("some error")""", "some error") diff --git a/tests/warn/i15503i.scala b/tests/warn/i15503i.scala index c7061d8a0b1c..8e622326e92a 100644 --- a/tests/warn/i15503i.scala +++ b/tests/warn/i15503i.scala @@ -188,12 +188,12 @@ package foo.test.i16822: } package foo.test.i16877: - import scala.Array // OK + import scala.collection.mutable.HashMap // OK import scala.annotation.StaticAnnotation // OK - class ExampleAnnotation(val a: Array[Int]) extends StaticAnnotation // OK + class ExampleAnnotation(val a: Object) extends StaticAnnotation // OK - @ExampleAnnotation(Array(1,2)) // OK + @ExampleAnnotation(new HashMap()) // OK class Test //OK package foo.test.i16926: