Skip to content

Commit

Permalink
Allow any applications in annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
mbovel committed Nov 5, 2024
1 parent 7668120 commit dbcf30f
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 139 deletions.
51 changes: 15 additions & 36 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
92 changes: 43 additions & 49 deletions tests/neg/annot-invalid.check
Original file line number Diff line number Diff line change
@@ -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
13 changes: 4 additions & 9 deletions tests/neg/annot-invalid.scala
Original file line number Diff line number Diff line change
@@ -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

()
78 changes: 44 additions & 34 deletions tests/neg/nowarn.check
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
3 changes: 3 additions & 0 deletions tests/neg/nowarn.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 1 addition & 4 deletions tests/neg/serialversionuid-not-const.scala
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion tests/pos/annot-valid.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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

()
2 changes: 1 addition & 1 deletion tests/neg/t1942.scala → tests/pos/t1942/A_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
3 changes: 3 additions & 0 deletions tests/pos/t1942/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Test {
println(new t)
}
Loading

0 comments on commit dbcf30f

Please sign in to comment.