From de0e3e7bc956ca0431290b5f20e025110671a8bb Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 13 Jan 2025 13:13:54 +0000 Subject: [PATCH] Allow lambdas in annotations --- .../src/dotty/tools/dotc/typer/Checking.scala | 30 +++++++++--- tests/neg/annot-invalid.check | 48 +++++-------------- tests/neg/annot-invalid.scala | 8 +--- tests/neg/i15054.scala | 15 ------ tests/pos/annot-15054.scala | 15 ++++++ tests/pos/annot-valid.scala | 6 +++ 6 files changed, 58 insertions(+), 64 deletions(-) delete mode 100644 tests/neg/i15054.scala create mode 100644 tests/pos/annot-15054.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0bdbaa92b322..b259fa4da648 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1430,7 +1430,7 @@ trait Checking { report.error(em"@${cls.name} needs a string literal as argument", arg.srcPos) tree case _ => - tree.find(!isValidAnnotSubtree(_)) match + findInvalidAnnotSubTree(tree) match case None => tree case Some(invalidSubTree) => errorTree( @@ -1441,11 +1441,9 @@ trait Checking { invalidSubTree.srcPos ) - /** Returns `true` if this tree can appear inside an annotation argument. */ - private def isValidAnnotSubtree(subTree: Tree) = - subTree.isType || subTree.isInstanceOf[ - EmptyTree.type - | Ident + private def findInvalidAnnotSubTree(tree: Tree)(using Context): Option[Tree] = + type ValidAnnotTree = + Ident | Select | This | Super @@ -1461,7 +1459,25 @@ trait Checking { | Inlined | Hole | Annotated - ] + | EmptyTree.type + + val accumulator = new TreeAccumulator[Option[Tree]]: + def apply(acc: Option[Tree], tree: Tree)(using Context): Option[Tree] = + if acc.isDefined then acc + else + tree match + case tree if tree.isType => foldOver(acc, tree) + case closureDef(meth) => + val paramsRes = + meth.paramss.foldLeft(acc): (acc: Option[Tree], params: List[ValDef] | List[TypeDef]) => + params.foldLeft(acc): (acc: Option[Tree], param: ValDef | TypeDef) => + foldOver(acc, param) + foldOver(paramsRes, meth.rhs) + case tree: ValidAnnotTree => foldOver(acc, tree) + case _ => Some(tree) + + accumulator(None, tree) + /** 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. diff --git a/tests/neg/annot-invalid.check b/tests/neg/annot-invalid.check index de972a906599..7f8d0ca900db 100644 --- a/tests/neg/annot-invalid.check +++ b/tests/neg/annot-invalid.check @@ -22,62 +22,38 @@ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: def f: Int = 2 | Type: (f : => Int) --- Error: tests/neg/annot-invalid.scala:13:30 -------------------------------------------------------------------------- -13 | val x5: Int @annot((x: Int) => x) = 0 // error - | ^^^^^^^^^^^^^ - | Implementation restriction: expression cannot be used inside an annotation argument. - | Tree: closure($anonfun) - | Type: Int => Int --- Error: tests/neg/annot-invalid.scala:14:21 -------------------------------------------------------------------------- -14 | val x6: Int @annot(O.g) = 0 // error - | ^^^ - | Implementation restriction: expression cannot be used inside an annotation argument. - | Tree: closure($anonfun) - | Type: Int => Int --- Error: tests/neg/annot-invalid.scala:16:25 -------------------------------------------------------------------------- -16 | val x7: Int @annot('{4}) = 0 // error +-- Error: tests/neg/annot-invalid.scala:14:25 -------------------------------------------------------------------------- +14 | val x5: Int @annot('{4}) = 0 // error | ^ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: '{4} | Type: (scala.quoted.Quotes) ?=> scala.quoted.Expr[Int] --- Error: tests/neg/annot-invalid.scala:18:9 --------------------------------------------------------------------------- -18 | @annot(new Object {}) val y1: Int = 0 // error +-- Error: tests/neg/annot-invalid.scala:16:9 --------------------------------------------------------------------------- +16 | @annot(new Object {}) val y1: Int = 0 // error | ^^^^^^^^^^^^^ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: final class $anon() extends Object() {} | Type: Object {...} --- Error: tests/neg/annot-invalid.scala:19:16 -------------------------------------------------------------------------- -19 | @annot({class C}) val y2: Int = 0 // error +-- Error: tests/neg/annot-invalid.scala:17:16 -------------------------------------------------------------------------- +17 | @annot({class C}) val y2: Int = 0 // error | ^^^^^^^ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: class C() extends Object() {} | Type: C --- Error: tests/neg/annot-invalid.scala:20:14 -------------------------------------------------------------------------- -20 | @annot({val y: Int = 2}) val y3: Int = 0 // error +-- Error: tests/neg/annot-invalid.scala:18:14 -------------------------------------------------------------------------- +18 | @annot({val y: Int = 2}) val y3: Int = 0 // error | ^^^^^^^^^^^^^^ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: val y: Int = 2 | Type: (y : Int) --- Error: tests/neg/annot-invalid.scala:21:14 -------------------------------------------------------------------------- -21 | @annot({def f = 2}) val y4: Int = 0 // error +-- Error: tests/neg/annot-invalid.scala:19:14 -------------------------------------------------------------------------- +19 | @annot({def f = 2}) val y4: Int = 0 // error | ^^^^^^^^^ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: def f: Int = 2 | Type: (f : => Int) --- Error: tests/neg/annot-invalid.scala:22:18 -------------------------------------------------------------------------- -22 | @annot((x: Int) => x) val y5: Int = 0 // error - | ^^^^^^^^^^^^^ - | Implementation restriction: expression cannot be used inside an annotation argument. - | Tree: closure($anonfun) - | Type: Int => Int --- Error: tests/neg/annot-invalid.scala:23:9 --------------------------------------------------------------------------- -23 | @annot(O.g) val y6: Int = 0 // error - | ^^^ - | Implementation restriction: expression cannot be used inside an annotation argument. - | Tree: closure($anonfun) - | Type: Int => Int --- Error: tests/neg/annot-invalid.scala:25:13 -------------------------------------------------------------------------- -25 | @annot('{4}) val y7: Int = 0 // error +-- Error: tests/neg/annot-invalid.scala:21:13 -------------------------------------------------------------------------- +21 | @annot('{4}) val y5: Int = 0 // error | ^ | Implementation restriction: expression cannot be used inside an annotation argument. | Tree: '{4} diff --git a/tests/neg/annot-invalid.scala b/tests/neg/annot-invalid.scala index 75e9aafd4fc4..81082541d3c2 100644 --- a/tests/neg/annot-invalid.scala +++ b/tests/neg/annot-invalid.scala @@ -10,18 +10,14 @@ def main = val x2: Int @annot({class C}) = 0 // error val x3: Int @annot({val y: Int = 2}) = 0 // error val x4: Int @annot({def f = 2}) = 0 // error - val x5: Int @annot((x: Int) => x) = 0 // error - val x6: Int @annot(O.g) = 0 // error def withQuotes(using Quotes) = - val x7: Int @annot('{4}) = 0 // error + val x5: Int @annot('{4}) = 0 // error @annot(new Object {}) val y1: Int = 0 // error @annot({class C}) val y2: Int = 0 // error @annot({val y: Int = 2}) val y3: Int = 0 // error @annot({def f = 2}) val y4: Int = 0 // error - @annot((x: Int) => x) val y5: Int = 0 // error - @annot(O.g) val y6: Int = 0 // error def withQuotes2(using Quotes) = - @annot('{4}) val y7: Int = 0 // error + @annot('{4}) val y5: Int = 0 // error () diff --git a/tests/neg/i15054.scala b/tests/neg/i15054.scala deleted file mode 100644 index 0bb1e4faa1b0..000000000000 --- a/tests/neg/i15054.scala +++ /dev/null @@ -1,15 +0,0 @@ -import scala.annotation.Annotation - -class AnAnnotation(function: Int => String) extends Annotation - -@AnAnnotation(_.toString) // error: expression cannot be used inside an annotation argument -val a = 1 -@AnAnnotation(_.toString.length.toString) // error: expression cannot be used inside an annotation argument -val b = 2 - -def test = - @AnAnnotation(_.toString) // error: expression cannot be used inside an annotation argument - val a = 1 - @AnAnnotation(_.toString.length.toString) // error: expression cannot be used inside an annotation argument - val b = 2 - a + b diff --git a/tests/pos/annot-15054.scala b/tests/pos/annot-15054.scala new file mode 100644 index 000000000000..cda6e497b8f8 --- /dev/null +++ b/tests/pos/annot-15054.scala @@ -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 diff --git a/tests/pos/annot-valid.scala b/tests/pos/annot-valid.scala index fd5134973e3c..148aaeaa861b 100644 --- a/tests/pos/annot-valid.scala +++ b/tests/pos/annot-valid.scala @@ -22,6 +22,9 @@ def main = 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((x: Int) => x) = 0 + val x17: Int @annot([T] => (x: T) => x) = 0 + val x18: Int @annot(O.g) = 0 @annot(42) val y1: Int = 0 @annot("hello") val y2: Int = 0 @@ -38,5 +41,8 @@ def main = @annot(throw new Error()) val y13: Int = 0 @annot(42: Double) val y14: Int = 0 @annot(O.g(2)) val y15: Int = 0 + @annot((x: Int) => x) val y16: Int = 0 + @annot([T] => (x: T) => x) val y17: Int = 0 + @annot(O.g) val y18: Int = 0 ()