Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lift arguments of explicitly constructed annotations #22553

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Comment on lines +539 to +540
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this flag be added to argCtx in

def argCtx(app: untpd.Tree)(using Context): Context =
instead ? That seems more generic. Also the reason for doing that should be documented (and the flag InAnnotation should document that it's turned off for arguments).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this flag be added to argCtx

No, that would remove the mode too early. argCtx is passed to ApplyToUntyped and is then used from the initializer of the parent class TypedApply, where we still need the mode to be set when typing an annotation.

So I think it's only in ApplyToUntyped.typedArg that we can remove the flag, which directly calls ProtoTypes.typedArg.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added documentation, tell me if that's clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks!

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)
Loading