Skip to content

Commit

Permalink
Lift arguments of explicitly constructed annotations (#22553)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbovel authored Feb 15, 2025
2 parents a601e83 + 337856c commit 43f8cdb
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 14 deletions.
30 changes: 30 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,36 @@ object Mode {
*/
val ForceInline: Mode = newMode(29, "ForceInline")

/** Are we typing the argument of an annotation?
*
* This mode is used through [[Applications.isAnnotConstr]] to avoid lifting
* arguments of annotation constructors. This mode is disabled in nested
* applications (from [[ProtoTypes.typedArg]]) and in "explicit" annotation
* constructors applications (annotation classes constructed with `new`).
*
* In the following example:
*
* ```scala
* @annot(y = new annot(y = Array("World"), x = 1), x = 2)
* ```
*
* the mode will be set when typing `@annot(...)` but not when typing
* `new annot(...)`, such that the arguments of the former are not lifted but
* the arguments of the later can be:
*
* ```scala
* @annot(x = 2, y = {
* val y$3: Array[String] =
* Array.apply[String](["World" : String]*)(
* scala.reflect.ClassTag.apply[String](classOf[String]))
* new annot(x = 1, y = y$3)
* })
* ```
*
* See #22035, #22526, #22553 and `dependent-annot-default-args.scala`.
*/
val InAnnotation: Mode = newMode(30, "InAnnotation")

/** Skip inlining of methods. */
val NoInline: Mode = newMode(31, "NoInline")
}
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,11 @@ trait Applications extends Compatibility {
sym.is(JavaDefined) && sym.isConstructor && sym.owner.is(JavaAnnotation)


/** Is `sym` a constructor of an annotation? */
def isAnnotConstr(sym: Symbol): Boolean =
sym.isConstructor && sym.owner.isAnnotation
/** Is `sym` a constructor of an annotation class, and are we in an
* annotation? If so, we don't lift arguments. See [[Mode.InAnnotation]].
*/
protected final def isAnnotConstr(sym: Symbol): Boolean =
ctx.mode.is(Mode.InAnnotation) && sym.isConstructor && sym.owner.isAnnotation

/** Match re-ordered arguments against formal parameters
* @param n The position of the first parameter in formals in `methType`.
Expand Down Expand Up @@ -994,9 +996,7 @@ trait Applications extends Compatibility {
case (arg: NamedArg, _) => arg
case (arg, name) => NamedArg(name, arg)
}
else if isAnnotConstr(methRef.symbol) then
typedArgs
else if !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then
else if !isAnnotConstr(methRef.symbol) && !sameSeq(args, orderedArgs) && !typedArgs.forall(isSafeArg) then
// need to lift arguments to maintain evaluation order in the
// presence of argument reorderings.
// (never do this for Java annotation constructors, hence the 'else if')
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@ object ProtoTypes {
def typedArg(arg: untpd.Tree, formal: Type)(using Context): Tree = {
val wideFormal = formal.widenExpr
val argCtx =
if wideFormal eq formal then ctx
else ctx.withNotNullInfos(ctx.notNullInfos.retractMutables)
if wideFormal eq formal then ctx.retractMode(Mode.InAnnotation)
else ctx.retractMode(Mode.InAnnotation).withNotNullInfos(ctx.notNullInfos.retractMutables)
val locked = ctx.typerState.ownedVars
val targ = cacheTypedArg(arg,
typer.typedUnadapted(_, wideFormal, locked)(using argCtx),
Expand Down
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2779,7 +2779,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def isInner(owner: Symbol) = owner == sym || sym.is(Param) && owner == sym.owner
val outer = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next()
def local: FreshContext = outer.fresh.setOwner(newLocalDummy(sym.owner))
sym.owner.infoOrCompleter match
val ctx0 = sym.owner.infoOrCompleter match
case completer: Namer#Completer
if sym.is(Param) && completer.completerTypeParams(sym).nonEmpty =>
// Create a new local context with a dummy owner and a scope containing the
Expand All @@ -2788,6 +2788,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
local.setScope(newScopeWith(completer.completerTypeParams(sym)*))
case _ =>
if outer.owner.isClass then local else outer
ctx0.addMode(Mode.InAnnotation)

def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = {
// necessary to force annotation trees to be computed.
Expand All @@ -2802,7 +2803,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
}

def typedAnnotation(annot: untpd.Tree)(using Context): Tree =
checkAnnotClass(checkAnnotArgs(typed(annot)))
val typedAnnot = withMode(Mode.InAnnotation)(typed(annot))
checkAnnotClass(checkAnnotArgs(typedAnnot))

def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit =
val annot = Annotations.Annotation(tree)
Expand Down Expand Up @@ -3335,7 +3337,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
end typedPackageDef

def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = {
val annot1 = checkAnnotClass(typedExpr(tree.annot))
val annot0 = withMode(Mode.InAnnotation)(typedExpr(tree.annot))
val annot1 = checkAnnotClass(annot0)
val annotCls = Annotations.annotClass(annot1)
if annotCls == defn.NowarnAnnot then
registerNowarn(annot1, tree)
Expand Down
77 changes: 75 additions & 2 deletions tests/printing/dependent-annot-default-args.check
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ package <empty> {
new dependent-annot-default-args$package()
final module class dependent-annot-default-args$package() extends Object() {
this: dependent-annot-default-args$package.type =>
def f(x: Int): Int @annot(x) = x
def f(x: Any): Any @annot(x) = x
def f2(x: Int):
Int @annot2(
y = Array.apply[Any](["Hello",x : Any]*)(scala.reflect.ClassTag.Any))
= x
def f3(x: Any, y: Any): Any @annot(x = x, y = y) = x
def test: Unit =
{
val y: Int = ???
val z: Int @annot(y) = f(y)
val z: Any @annot(y) = f(y)
val z2:
Int @annot2(
y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)
Expand All @@ -41,6 +42,78 @@ package <empty> {
@annot2(
y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any))
val z4: Int = 45
val z5: annot =
{
val y$1: Array[String] =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
new annot(x = 1, y = y$1)
}
val z6: annot2 =
{
val y$2: Array[Any] =
Array.apply[Any](["World" : Any]*)(scala.reflect.ClassTag.Any)
new annot2(x = 1, y = y$2)
}
@annot(x = 2,
y =
{
val y$3: Array[String] =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
new annot(x = 1, y = y$3)
}
) val z7: Int = 45
@annot(x = 4,
y =
3:
Int @annot(x = 1,
y =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
)
) val z8: Int = 45
val z9:
Int @annot(x = 2,
y =
{
val y$4: Array[String] =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
new annot(x = 1, y = y$4)
}
)
= 46
@annot(x = 4,
y =
3:
Int @annot(x = 1,
y =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
)
) val z10: Int = 45
val z11: Any @annot(annot) =
f(
{
val y$5: Array[String] =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
new annot(x = 1, y = y$5)
}
)
val z12: Any @annot(x = x, y = y) =
f3(
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String])),
1)
val z13: Any @annot(x = x, y = y) =
{
val y$6: Array[String] =
Array.apply[String](["World" : String]*)(
scala.reflect.ClassTag.apply[String](classOf[String]))
f3(x = 1, y = y$6)
}
()
}
}
Expand Down
14 changes: 13 additions & 1 deletion tests/printing/dependent-annot-default-args.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
class annot(x: Any, y: Any = 42) extends annotation.Annotation
class annot2(x: Any = -1, y: Array[Any] = Array("Hello")) extends annotation.Annotation

def f(x: Int): Int @annot(x) = x
def f(x: Any): Any @annot(x) = x
def f2(x: Int): Int @annot2(y = Array("Hello", x)) = x
def f3(x: Any, y: Any): Any @annot(y=y, x=x) = x

def test =
val y: Int = ???
Expand All @@ -13,3 +14,14 @@ def test =
@annot(44) val z3 = 45
@annot2(y = Array("Hello", y)) val z4 = 45

// Arguments are still lifted if the annotation class is instantiated
// explicitly. See #22526.
val z5 = new annot(y = Array("World"), x = 1)
val z6 = new annot2(y = Array("World"), x = 1)
@annot(y = new annot(y = Array("World"), x = 1), x = 2) val z7 = 45
@annot(y = 3: Int @annot(y = Array("World"), x = 1), x = 4) val z8 = 45
val z9: Int @annot(y = new annot(y = Array("World"), x = 1), x = 2) = 46
@annot(y = 3: Int @annot(y = Array("World"), x = 1), x = 4) val z10 = 45
val z11 = f(new annot(y = Array("World"), x = 1))
val z12 = f3(Array("World"), 1)
val z13 = f3(y=Array("World"), x=1)

0 comments on commit 43f8cdb

Please sign in to comment.