From a9557b694bffeb978429cc772eb2481e43760bea Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Fri, 7 Feb 2025 16:58:36 +0000 Subject: [PATCH] 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 | 9 +++++++++ tests/printing/dependent-annot-default-args.scala | 3 +++ 5 files changed, 28 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..05106ef512ab 100644 --- a/tests/printing/dependent-annot-default-args.check +++ b/tests/printing/dependent-annot-default-args.check @@ -41,6 +41,15 @@ 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) + } + val z6: annot2 = + new annot2(x = 1, + y = Array.apply[Any](["World" : Any]*)(scala.reflect.ClassTag.Any)) () } } 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)