Skip to content

Commit 408638c

Browse files
committed
Check annotation arguments
1 parent 0f9e502 commit 408638c

20 files changed

+260
-26
lines changed

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,17 @@ object TreeChecker {
827827
|${mismatch.message}${mismatch.explanation}
828828
|tree = $tree ${tree.className}""".stripMargin
829829
})
830+
checkWellFormedType(tp1)
831+
checkWellFormedType(tp2)
832+
833+
/** Check that the type `tp` is well-formed. Currently this only means
834+
* checking that annotated types have valid annotation arguments.
835+
*/
836+
private def checkWellFormedType(tp: Type)(using Context): Unit =
837+
tp.foreachPart:
838+
case AnnotatedType(underlying, annot) => checkAnnot(annot.tree)
839+
case _ => ()
840+
830841
}
831842

832843
/** Tree checker that can be applied to a local tree. */

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,12 +1392,21 @@ trait Checking {
13921392
if !Inlines.inInlineMethod && !ctx.isInlineContext then
13931393
report.error(em"$what can only be used in an inline method", pos)
13941394

1395+
def checkAnnot(tree: Tree)(using Context): Tree =
1396+
tree match
1397+
case Ident(tpnme.BOUNDTYPE_ANNOT) =>
1398+
// `FirstTransform.toTypeTree` creates `Annotated` nodes whose `annot` are
1399+
// `Ident`s, not annotation instances. See `tests/pos/annot-boundtype.scala`.
1400+
tree
1401+
case _ =>
1402+
checkAnnotTree(checkAnnotClass(tree))
1403+
13951404
/** Check that the class corresponding to this tree is either a Scala or Java annotation.
13961405
*
13971406
* @return The original tree or an error tree in case `tree` isn't a valid
13981407
* annotation or already an error tree.
13991408
*/
1400-
def checkAnnotClass(tree: Tree)(using Context): Tree =
1409+
private def checkAnnotClass(tree: Tree)(using Context): Tree =
14011410
if tree.tpe.isError then
14021411
return tree
14031412
val cls = Annotations.annotClass(tree)
@@ -1409,8 +1418,8 @@ trait Checking {
14091418
errorTree(tree, em"$cls is not a valid Scala annotation: it does not extend `scala.annotation.Annotation`")
14101419
else tree
14111420

1412-
/** Check arguments of compiler-defined annotations */
1413-
def checkAnnotArgs(tree: Tree)(using Context): tree.type =
1421+
/** Check arguments of annotations */
1422+
private def checkAnnotTree(tree: Tree)(using Context): Tree =
14141423
val cls = Annotations.annotClass(tree)
14151424
tree match
14161425
case Apply(tycon, arg :: Nil) if cls == defn.TargetNameAnnot =>
@@ -1424,8 +1433,57 @@ trait Checking {
14241433
arg.tpe.widenTermRefExpr.normalized match
14251434
case _: ConstantType => ()
14261435
case _ => report.error(em"@${cls.name} requires constant expressions as a parameter", arg.srcPos)
1427-
case _ =>
1428-
tree
1436+
case _ => ()
1437+
1438+
findInvalidAnnotSubTree(tree) match
1439+
case None => tree
1440+
case Some(invalidSubTree) =>
1441+
errorTree(
1442+
EmptyTree,
1443+
em"""Expression cannot be used inside an annotation argument.
1444+
|Tree: ${invalidSubTree}
1445+
|Type: ${invalidSubTree.tpe}""",
1446+
invalidSubTree.srcPos
1447+
)
1448+
1449+
private def findInvalidAnnotSubTree(tree: Tree)(using Context): Option[Tree] =
1450+
type ValidAnnotTree =
1451+
Ident
1452+
| Select
1453+
| This
1454+
| Super
1455+
| Apply
1456+
| TypeApply
1457+
| Literal
1458+
| New
1459+
| Typed
1460+
| NamedArg
1461+
| Assign
1462+
| Block
1463+
| SeqLiteral
1464+
| Inlined
1465+
| Hole
1466+
| Annotated
1467+
| EmptyTree.type
1468+
1469+
val accumulator = new TreeAccumulator[Option[Tree]]:
1470+
override def apply(acc: Option[Tree], tree: Tree)(using Context): Option[Tree] =
1471+
if acc.isDefined then
1472+
acc
1473+
else
1474+
tree match
1475+
case tree if tree.isType => foldOver(acc, tree)
1476+
case closureDef(meth) =>
1477+
val paramsRes =
1478+
meth.paramss.foldLeft(acc): (acc: Option[Tree], params: List[ValDef] | List[TypeDef]) =>
1479+
params.foldLeft(acc): (acc: Option[Tree], param: ValDef | TypeDef) =>
1480+
foldOver(acc, param)
1481+
foldOver(paramsRes, meth.rhs)
1482+
case tree: ValidAnnotTree => foldOver(acc, tree)
1483+
case _ => Some(tree)
1484+
1485+
accumulator(None, tree)
1486+
14291487

14301488
/** 1. Check that all case classes that extend `scala.reflect.Enum` are `enum` cases
14311489
* 2. Check that parameterised `enum` cases do not extend java.lang.Enum.
@@ -1674,7 +1732,7 @@ trait NoChecking extends ReChecking {
16741732
override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = ()
16751733
override def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = ()
16761734
override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp
1677-
override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree
1735+
override def checkAnnot(tree: Tree)(using Context): tree.type = tree
16781736
override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = ()
16791737
override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = ()
16801738
override def checkSimpleKinded(tpt: Tree)(using Context): Tree = tpt

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,7 +2799,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27992799
}
28002800

28012801
def typedAnnotation(annot: untpd.Tree)(using Context): Tree =
2802-
checkAnnotClass(checkAnnotArgs(typed(annot)))
2802+
checkAnnot(typed(annot))
28032803

28042804
def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit =
28052805
val annot = Annotations.Annotation(tree)
@@ -3330,7 +3330,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
33303330
end typedPackageDef
33313331

33323332
def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = {
3333-
val annot1 = checkAnnotClass(typedExpr(tree.annot))
3333+
val annot1 = checkAnnot(typedExpr(tree.annot))
33343334
val annotCls = Annotations.annotClass(annot1)
33353335
if annotCls == defn.NowarnAnnot then
33363336
registerNowarn(annot1, tree)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import scala.quoted.*
22
class Test {
33
def foo(str: Expr[String])(using Quotes) = '{
4-
@deprecated($str, "")
4+
@deprecated($str, "") // error: expression cannot be used inside an annotation argument
55
def bar = ???
66
}
77
}

tests/neg-macros/i7121.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import scala.quoted.*
22

3-
class annot1[T](x: Expr[T]) extends scala.annotation.Annotation
43
class annot2[T: Type](x: T) extends scala.annotation.Annotation
54

65
class Test()(implicit qtx: Quotes) {
7-
@annot1('{4}) // error
8-
def foo(str: String) = ()
9-
106
@annot2(4)(using Type.of[Int]) // error
117
def foo2(str: String) = ()
12-
138
}

tests/neg-macros/i7121b.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted.*
2+
3+
class annot1[T](x: Expr[T]) extends scala.annotation.Annotation
4+
5+
class Test()(implicit qtx: Quotes) {
6+
@annot1('{4}) // error: expression cannot be used inside an annotation argument
7+
def foo(str: String) = ()
8+
}

tests/pos-macros/i7519b.scala renamed to tests/neg-macros/i7519b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ inline def quote[T]: Quoted[T] = ${ quoteImpl[T] }
99

1010
def quoteImpl[T: Type](using Quotes): Expr[Quoted[T]] = {
1111
val value: Expr[Int] = '{ 42 }
12-
'{ new Quoted[T @Annot($value)] }
12+
'{ new Quoted[T @Annot($value)] } // error: expression cannot be used inside an annotation argument
1313
}

tests/neg/annot-invalid.check

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
-- Error: tests/neg/annot-invalid.scala:9:21 ---------------------------------------------------------------------------
2+
9 | val x1: Int @annot(new Object {}) = 0 // error
3+
| ^^^^^^^^^^^^^
4+
| Expression cannot be used inside an annotation argument.
5+
| Tree: final class $anon() extends Object() {}
6+
| Type: Object {...}
7+
-- Error: tests/neg/annot-invalid.scala:10:28 --------------------------------------------------------------------------
8+
10 | val x2: Int @annot({class C}) = 0 // error
9+
| ^^^^^^^
10+
| Expression cannot be used inside an annotation argument.
11+
| Tree: class C() extends Object() {}
12+
| Type: C
13+
-- Error: tests/neg/annot-invalid.scala:11:26 --------------------------------------------------------------------------
14+
11 | val x3: Int @annot({val y: Int = 2}) = 0 // error
15+
| ^^^^^^^^^^^^^^
16+
| Expression cannot be used inside an annotation argument.
17+
| Tree: val y: Int = 2
18+
| Type: (y : Int)
19+
-- Error: tests/neg/annot-invalid.scala:12:26 --------------------------------------------------------------------------
20+
12 | val x4: Int @annot({def f = 2}) = 0 // error
21+
| ^^^^^^^^^
22+
| Expression cannot be used inside an annotation argument.
23+
| Tree: def f: Int = 2
24+
| Type: (f : => Int)
25+
-- Error: tests/neg/annot-invalid.scala:14:25 --------------------------------------------------------------------------
26+
14 | val x5: Int @annot('{4}) = 0 // error
27+
| ^
28+
| Expression cannot be used inside an annotation argument.
29+
| Tree: '{4}
30+
| Type: (scala.quoted.Quotes) ?=> scala.quoted.Expr[Int]
31+
-- Error: tests/neg/annot-invalid.scala:16:9 ---------------------------------------------------------------------------
32+
16 | @annot(new Object {}) val y1: Int = 0 // error
33+
| ^^^^^^^^^^^^^
34+
| Expression cannot be used inside an annotation argument.
35+
| Tree: final class $anon() extends Object() {}
36+
| Type: Object {...}
37+
-- Error: tests/neg/annot-invalid.scala:17:16 --------------------------------------------------------------------------
38+
17 | @annot({class C}) val y2: Int = 0 // error
39+
| ^^^^^^^
40+
| Expression cannot be used inside an annotation argument.
41+
| Tree: class C() extends Object() {}
42+
| Type: C
43+
-- Error: tests/neg/annot-invalid.scala:18:14 --------------------------------------------------------------------------
44+
18 | @annot({val y: Int = 2}) val y3: Int = 0 // error
45+
| ^^^^^^^^^^^^^^
46+
| Expression cannot be used inside an annotation argument.
47+
| Tree: val y: Int = 2
48+
| Type: (y : Int)
49+
-- Error: tests/neg/annot-invalid.scala:19:14 --------------------------------------------------------------------------
50+
19 | @annot({def f = 2}) val y4: Int = 0 // error
51+
| ^^^^^^^^^
52+
| Expression cannot be used inside an annotation argument.
53+
| Tree: def f: Int = 2
54+
| Type: (f : => Int)
55+
-- Error: tests/neg/annot-invalid.scala:21:13 --------------------------------------------------------------------------
56+
21 | @annot('{4}) val y5: Int = 0 // error
57+
| ^
58+
| Expression cannot be used inside an annotation argument.
59+
| Tree: '{4}
60+
| Type: (scala.quoted.Quotes) ?=> scala.quoted.Expr[Int]

tests/neg/annot-invalid.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted.Quotes
2+
3+
class annot[T](arg: T) extends scala.annotation.Annotation
4+
5+
def main =
6+
object O:
7+
def g(x: Int): Int = x
8+
9+
val x1: Int @annot(new Object {}) = 0 // error
10+
val x2: Int @annot({class C}) = 0 // error
11+
val x3: Int @annot({val y: Int = 2}) = 0 // error
12+
val x4: Int @annot({def f = 2}) = 0 // error
13+
def withQuotes(using Quotes) =
14+
val x5: Int @annot('{4}) = 0 // error
15+
16+
@annot(new Object {}) val y1: Int = 0 // error
17+
@annot({class C}) val y2: Int = 0 // error
18+
@annot({val y: Int = 2}) val y3: Int = 0 // error
19+
@annot({def f = 2}) val y4: Int = 0 // error
20+
def withQuotes2(using Quotes) =
21+
@annot('{4}) val y5: Int = 0 // error
22+
23+
()

tests/neg/i7740a.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class A(a: Any) extends annotation.StaticAnnotation
2+
@A({val x = 0}) trait B // error: expression cannot be used inside an annotation argument

0 commit comments

Comments
 (0)