From 5f8ae12e75c237db1a51bb17ad1a61a5f4dac303 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 7 Feb 2025 16:58:36 +0000 Subject: [PATCH 1/2] Lift arguments of explicitly constructed annotations --- compiler/src/dotty/tools/dotc/core/Mode.scala | 3 +++ .../src/dotty/tools/dotc/typer/Applications.scala | 13 +++++++------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 9 ++++++--- tests/printing/dependent-annot-default-args.check | 6 ++++++ tests/printing/dependent-annot-default-args.scala | 3 +++ 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index cce162092168..59b536f07c07 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -166,6 +166,9 @@ object Mode { */ val ForceInline: Mode = newMode(29, "ForceInline") + /** Are we typing an annotation? */ + val InAnnotation: Mode = newMode(30, "InAnnotation") + /** Skip inlining of methods. */ val NoInline: Mode = newMode(31, "NoInline") } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98bfbe69ff8c..007a2c971b41 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -694,9 +694,12 @@ 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 #22035, #22526 and `dependent-annot-default-args.scala`. + */ + 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`. @@ -994,9 +997,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') diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6e0651128e8e..4a3b67ae8490 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 @@ -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. @@ -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) @@ -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) diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check index f457d5d62edb..f98047f16a15 100644 --- a/tests/printing/dependent-annot-default-args.check +++ b/tests/printing/dependent-annot-default-args.check @@ -41,6 +41,12 @@ package { @annot2( y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)) val z4: Int = 45 + val z5: annot2 = + { + val y$1: Array[Any] = + Array.apply[Any](["World" : Any]*)(scala.reflect.ClassTag.Any) + new annot2(x = 1, y = y$1) + } () } } diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala index 11fc9ef52cc9..c607808deda0 100644 --- a/tests/printing/dependent-annot-default-args.scala +++ b/tests/printing/dependent-annot-default-args.scala @@ -13,3 +13,6 @@ 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 annot2(y = Array("World"), x = 1) From 337856c917afba5ca06645ad443a7499e8afc68f Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 10 Feb 2025 12:44:33 +0000 Subject: [PATCH 2/2] Handle nested explicitly constructed annotations --- compiler/src/dotty/tools/dotc/core/Mode.scala | 29 ++++++- .../dotty/tools/dotc/typer/Applications.scala | 3 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 4 +- .../dependent-annot-default-args.check | 77 +++++++++++++++++-- .../dependent-annot-default-args.scala | 13 +++- 5 files changed, 114 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 59b536f07c07..6fd76e37977d 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -166,7 +166,34 @@ object Mode { */ val ForceInline: Mode = newMode(29, "ForceInline") - /** Are we typing an annotation? */ + /** 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. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 007a2c971b41..2b67bfe61a81 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -695,8 +695,7 @@ trait Applications extends Compatibility { /** Is `sym` a constructor of an annotation class, and are we in an - * annotation? If so, we don't lift arguments. - * See #22035, #22526 and `dependent-annot-default-args.scala`. + * 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 diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 85f44ead5f28..cf3867701c7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -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), diff --git a/tests/printing/dependent-annot-default-args.check b/tests/printing/dependent-annot-default-args.check index f98047f16a15..ccb988e83663 100644 --- a/tests/printing/dependent-annot-default-args.check +++ b/tests/printing/dependent-annot-default-args.check @@ -23,15 +23,16 @@ package { 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) @@ -41,11 +42,77 @@ package { @annot2( y = Array.apply[Any](["Hello",y : Any]*)(scala.reflect.ClassTag.Any)) val z4: Int = 45 - val z5: annot2 = + val z5: annot = { - val y$1: Array[Any] = + 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$1) + 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) } () } diff --git a/tests/printing/dependent-annot-default-args.scala b/tests/printing/dependent-annot-default-args.scala index c607808deda0..f3cb2a82c910 100644 --- a/tests/printing/dependent-annot-default-args.scala +++ b/tests/printing/dependent-annot-default-args.scala @@ -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 = ??? @@ -15,4 +16,12 @@ def test = // Arguments are still lifted if the annotation class is instantiated // explicitly. See #22526. - val z5 = new annot2(y = Array("World"), x = 1) + 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)