Skip to content

Commit

Permalink
Allow New, Select, Block, ValDef, DefDef, Closure and EmptyTree
Browse files Browse the repository at this point in the history
And use a TreeMap for the annotation check
  • Loading branch information
mbovel committed Nov 22, 2024
1 parent d9ba132 commit 872459d
Show file tree
Hide file tree
Showing 15 changed files with 129 additions and 107 deletions.
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -499,9 +499,6 @@ class Definitions {

@tu lazy val DummyImplicitClass: ClassSymbol = requiredClass("scala.DummyImplicit")

@tu lazy val SymbolModule: Symbol = requiredModule("scala.Symbol")
@tu lazy val JSSymbolModule: Symbol = requiredModule("scala.scalajs.js.Symbol")

@tu lazy val ScalaRuntimeModule: Symbol = requiredModule("scala.runtime.ScalaRunTime")
def runtimeMethodRef(name: PreName): TermRef = ScalaRuntimeModule.requiredMethodRef(name)
def ScalaRuntime_drop: Symbol = runtimeMethodRef(nme.drop).symbol
Expand Down
73 changes: 38 additions & 35 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1388,12 +1388,12 @@ trait Checking {

def checkAnnot(tree: Tree)(using Context): Tree =
tree match
case Ident(tpnme.BOUNDTYPE_ANNOT) =>
case Ident(tpnme.BOUNDTYPE_ANNOT) =>
// `FirstTransform.toTypeTree` creates `Annotated` nodes whose `annot` are
// `Ident`s, not annotation instances. See `tests/pos/annot-boundtype.scala`.
tree
case _ =>
checkAnnotArgs(checkAnnotClass(tree))
case _ =>
checkAnnotTree(checkAnnotClass(tree))

/** Check that the class corresponding to this tree is either a Scala or Java annotation.
*
Expand All @@ -1413,7 +1413,7 @@ trait Checking {
else tree

/** Check arguments of annotations */
private def checkAnnotArgs(tree: Tree)(using Context): Tree =
private def checkAnnotTree(tree: Tree)(using Context): Tree =
val cls = Annotations.annotClass(tree)
tree match
case Apply(tycon, arg :: Nil) if cls == defn.TargetNameAnnot =>
Expand All @@ -1424,40 +1424,43 @@ trait Checking {
case _ =>
report.error(em"@${cls.name} needs a string literal as argument", arg.srcPos)
tree
case _ if cls.isRetainsLike =>
tree
case _ =>
if cls.isRetainsLike then tree
else
tpd.allTermArguments(tree).foldLeft(tree: Tree)((acc: Tree, arg: Tree) =>
if validAnnotArg(arg) then acc
else errorTree(
checkAnnotTreeMap.transform(tree)

private def checkAnnotTreeMap(using Context) =
new TreeMap:
override def transform(tree: Tree)(using Context): Tree =
tree match
case _ if tree.isType =>
super.transform(tree)
case _: ( Literal
| Ident
| New
| Select
| Apply
| TypeApply
| SeqLiteral
| Typed
| Block
| ValDef
| DefDef
| Closure
| NamedArg
| EmptyTree.type
| Splice
| Hole) =>
super.transform(tree)
case _ =>
errorTree(
EmptyTree,
em"""Implementation restriction: not a valid annotation argument.
|Argument: $arg
|Type: ${arg.tpe}""",
arg.srcPos
em"""Implementation restriction: this tree cannot be used in an annotation.
|Tree: ${tree}
|Type: ${tree.tpe}""",
tree.srcPos
)
)

private def validAnnotArg(t: Tree)(using Context): Boolean =
t match
case _ if t.tpe.isEffectivelySingleton => true
case Literal(_) => true
// `Ident(nme.WILDCARD)` 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) => validAnnotArg(fun) && args.forall(validAnnotArg)
case TypeApply(fun, args) => validAnnotArg(fun)
case SeqLiteral(elems, _) => elems.forall(validAnnotArg)
case Typed(expr, _) => validAnnotArg(expr)
// TODO(mbovel): should probably be handled by `tpd.allTermArguments` instead.
case NamedArg(_, arg) => validAnnotArg(arg)
// TODO(mbovel): do we really want to allow `Splice` and `Hole`?
// When removing those cases, tests/pos-macros/i7519b.scala and
// tests/pos-macros/i7052.scala fail.
case Splice(_) => true
case Hole(_, _, _, _) => true
case _ => false


/** 1. Check that all case classes that extend `scala.reflect.Enum` are `enum` cases
* 2. Check that parameterised `enum` cases do not extend java.lang.Enum.
* 3. Check that only a static `enum` base class can extend java.lang.Enum.
Expand Down
64 changes: 20 additions & 44 deletions tests/neg/annot-invalid.check
Original file line number Diff line number Diff line change
@@ -1,48 +1,24 @@
-- 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: {
| 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: {
| val x: Int = 1
| ()
| }
| Type: Unit
-- Error: tests/neg/annot-invalid.scala:6:21 ---------------------------------------------------------------------------
6 | val x3: Int @annot((x: Int) => x) = 0 // error
| ^^^^^^^^^^^^^
| Implementation restriction: not a valid annotation argument.
| 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: this tree cannot be used in an annotation.
| Tree: final class $anon() extends Object() {}
| Type: Object {...}
-- Error: tests/neg/annot-invalid.scala:5:28 ---------------------------------------------------------------------------
5 | val x2: Int @annot({class C}) = 0 // error
| ^^^^^^^
| Implementation restriction: this tree cannot be used in an annotation.
| Tree: class C() extends Object() {}
| Type: C
-- Error: tests/neg/annot-invalid.scala:7:9 ----------------------------------------------------------------------------
7 | @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 y3: Int = 0 // error
| ^^^^^^^^^^^^^
| Implementation restriction: not a valid annotation argument.
| Argument: (x: Int) => x
| Type: Int => Int
| Implementation restriction: this tree cannot be used in an annotation.
| Tree: final class $anon() extends Object() {}
| Type: Object {...}
-- Error: tests/neg/annot-invalid.scala:8:16 ---------------------------------------------------------------------------
8 | @annot({class C}) val y2: Int = 0 // error
| ^^^^^^^
| Implementation restriction: this tree cannot be used in an annotation.
| Tree: class C() extends Object() {}
| Type: C
6 changes: 2 additions & 4 deletions tests/neg/annot-invalid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ class annot[T](arg: T) extends scala.annotation.Annotation

def main =
val x1: Int @annot(new Object {}) = 0 // error
val x2: Int @annot({val x = 1}) = 0 // error
val x3: Int @annot((x: Int) => x) = 0 // error
val x2: Int @annot({class C}) = 0 // error

@annot(new Object {}) val y1: Int = 0 // error
@annot({val x = 1}) val y2: Int = 0 // error
@annot((x: Int) => x) val y3: Int = 0 // error
@annot({class C}) val y2: Int = 0 // error

()
15 changes: 0 additions & 15 deletions tests/neg/i15054.scala

This file was deleted.

2 changes: 0 additions & 2 deletions tests/neg/i7740a.scala

This file was deleted.

2 changes: 0 additions & 2 deletions tests/neg/i7740b.scala

This file was deleted.

10 changes: 10 additions & 0 deletions tests/pos/annot-17939b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import scala.annotation.Annotation
class myRefined(f: ? => Boolean) extends Annotation

def test(axes: Int) = true

trait Tensor:
def mean(axes: Int): Int @myRefined(_ => test(axes))

class TensorImpl() extends Tensor:
def mean(axes: Int) = ???
1 change: 0 additions & 1 deletion tests/pos/annot-boundtype.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ def f2(t: Tuple) =
t match
case _: (t *: ts) => ()
case _ => ()

15 changes: 14 additions & 1 deletion tests/pos/annot-valid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ def main =
val n: Int = 0
def f(x: Any): Unit = ()

object O:
def g(x: Any): Unit = ()

val x1: Int @annot(42) = 0
val x2: Int @annot("hello") = 0
val x3: Int @annot(classOf[Int]) = 0
Expand All @@ -18,7 +21,12 @@ def main =
val x12: Int @annot(f(2)) = 0
val x13: Int @annot(throw new Error()) = 0
val x14: Int @annot(42: Double) = 0

val x15: Int @annot(O.g(2)) = 0
val x16: Int @annot({val y: Int = 2}) = 0
val x17: Int @annot({def f = 2}) = 0
val x18: Int @annot((x: Int) => x) = 0
val x19: Int @annot(O.g) = 0

@annot(42) val y1: Int = 0
@annot("hello") val y2: Int = 0
@annot(classOf[Int]) val y3: Int = 0
Expand All @@ -33,5 +41,10 @@ def main =
@annot(f(2)) val y12: Int = 0
@annot(throw new Error()) val y13: Int = 0
@annot(42: Double) val y14: Int = 0
@annot(O.g(2)) val y15: Int = 0
@annot({val y: Int = 2}) val y16: Int = 0
@annot({def f = 2}) val y17: Int = 0
@annot((x: Int) => x) val y18: Int = 0
@annot(O.g) val y19: Int = 0

()
15 changes: 15 additions & 0 deletions tests/pos/i15054.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import scala.annotation.Annotation

class AnAnnotation(function: Int => String) extends Annotation

@AnAnnotation(_.toString)
val a = 1
@AnAnnotation(_.toString.length.toString)
val b = 2

def test =
@AnAnnotation(_.toString)
val a = 1
@AnAnnotation(_.toString.length.toString)
val b = 2
a + b
2 changes: 2 additions & 0 deletions tests/pos/i7740a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class A(a: Any) extends annotation.StaticAnnotation
@A({val x = 0}) trait B
2 changes: 2 additions & 0 deletions tests/pos/i7740b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class A(a: Any) extends annotation.StaticAnnotation
@A({def x = 0}) trait B
19 changes: 19 additions & 0 deletions tests/printing/annot-19846b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[[syntax trees at end of typer]] // tests/printing/annot-19846b.scala
package <empty> {
class lambdaAnnot(g: () => Int) extends scala.annotation.Annotation(),
annotation.StaticAnnotation {
private[this] val g: () => Int
}
final lazy module val Test: Test = new Test()
final module class Test() extends Object() { this: Test.type =>
val y: Int = ???
val z: Int @lambdaAnnot(() => Test.y) = f(Test.y)
}
final lazy module val annot-19846b$package: annot-19846b$package =
new annot-19846b$package()
final module class annot-19846b$package() extends Object() {
this: annot-19846b$package.type =>
def f(x: Int): Int @lambdaAnnot(() => x) = x
}
}

7 changes: 7 additions & 0 deletions tests/printing/annot-19846b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class lambdaAnnot(g: () => Int) extends annotation.StaticAnnotation

def f(x: Int): Int @lambdaAnnot(() => x) = x

object Test:
val y: Int = ???
val z /* : Int @lambdaAnnot(() => y) */ = f(y)

0 comments on commit 872459d

Please sign in to comment.