From 6ac64bbe2dcbe3a6e4321f217bf06a9bd6347ac9 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 7 Jan 2026 16:37:16 -0800 Subject: [PATCH] Also copy param annots at mixin Co-authored-by: ecrabor8 --- .../src/dotty/tools/dotc/core/SymUtils.scala | 4 +-- .../dotty/tools/dotc/transform/Mixin.scala | 5 ++- .../dotty/tools/dotc/transform/MixinOps.scala | 20 +++++++---- tests/run/i22991/Bar.scala | 10 ++++++ tests/run/i22991/Foo.java | 5 +++ tests/run/i22991/Test.scala | 34 +++++++++++++++++++ 6 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 tests/run/i22991/Bar.scala create mode 100644 tests/run/i22991/Foo.java create mode 100644 tests/run/i22991/Test.scala diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index c9b92472f707..45727deaf3f9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -42,8 +42,8 @@ class SymUtils: } /** All traits implemented by a class, except for those inherited through the superclass. - * The empty list if `self` is a trait. - */ + * The empty list if `self` is a trait. + */ def mixins(using Context): List[ClassSymbol] = if (self.is(Trait)) Nil else directlyInheritedTraits diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index a9813ec90b1a..a28be1c5d808 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -312,11 +312,10 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => yield transformFollowing(DefDef(mkForwarderSym(setter.asTerm), unitLiteral.withSpan(cls.span))) def mixinForwarders(mixin: ClassSymbol): List[Tree] = - for (meth <- mixin.info.decls.toList if needsMixinForwarder(meth)) - yield { + for meth <- mixin.info.decls.filter(needsMixinForwarder) + yield util.Stats.record("mixin forwarders") transformFollowing(DefDef(mkMixinForwarderSym(meth.asTerm), forwarderRhsFn(meth))) - } def mkMixinForwarderSym(target: TermSymbol): TermSymbol = val sym = mkForwarderSym(target, extraFlags = MixedIn) diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index 3a3fa65ff64b..5a7dd3254c9b 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -2,9 +2,10 @@ package dotty.tools.dotc package transform import core.* -import Symbols.*, Types.*, Contexts.*, DenotTransformers.*, Flags.* +import Decorators.*, Symbols.*, Types.*, Contexts.*, DenotTransformers.*, Flags.* import NameKinds.* import util.Spans.* +import util.chaining.* import StdNames.*, NameOps.* import typer.Nullables @@ -19,15 +20,20 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) { map(n => getClassIfDefined("org.junit." + n)). filter(_.exists) - def mkForwarderSym(member: TermSymbol, extraFlags: FlagSet = EmptyFlags): TermSymbol = { - val res = member.copy( + def mkForwarderSym(member: TermSymbol, extraFlags: FlagSet = EmptyFlags): TermSymbol = + member.copy( owner = cls, name = member.name.stripScala2LocalSuffix, flags = member.flags &~ Deferred &~ Module | Synthetic | extraFlags, - info = cls.thisType.memberInfo(member)).enteredAfter(thisPhase).asTerm - res.addAnnotations(member.annotations.filter(_.symbol != defn.TailrecAnnot)) - res - } + info = cls.thisType.memberInfo(member) + ) + .enteredAfter(thisPhase) + .asTerm.tap: res => + res.addAnnotations(member.annotations.filter(_.symbol != defn.TailrecAnnot)) + res.setParamss(res.paramSymss) + atPhaseBeforeTransforms: + for (src, dst) <- member.paramSymss.flatten.filter(!_.isType).zip(res.paramSymss.flatten) do + dst.addAnnotations(src.annotations) def superRef(target: Symbol, span: Span = cls.span): Tree = { val sup = if (target.isConstructor && !target.owner.is(Trait)) diff --git a/tests/run/i22991/Bar.scala b/tests/run/i22991/Bar.scala new file mode 100644 index 000000000000..1611544fd706 --- /dev/null +++ b/tests/run/i22991/Bar.scala @@ -0,0 +1,10 @@ +class Blah extends scala.annotation.StaticAnnotation + +trait Barly { + def bar[T](a: String, @Foo v: Int)(@Foo b: T, @Blah w: Int) = () + extension [T](s: String) def f[U](@Foo t: T, @Foo u: U) = () +} + +class Bar extends Barly { + def bar2(@Foo v: Int) = () +} diff --git a/tests/run/i22991/Foo.java b/tests/run/i22991/Foo.java new file mode 100644 index 000000000000..ac4e8460ac3a --- /dev/null +++ b/tests/run/i22991/Foo.java @@ -0,0 +1,5 @@ +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Foo { +} diff --git a/tests/run/i22991/Test.scala b/tests/run/i22991/Test.scala new file mode 100644 index 000000000000..e1de929db7b3 --- /dev/null +++ b/tests/run/i22991/Test.scala @@ -0,0 +1,34 @@ +// scalajs: --skip +//Test java runtime reflection access to @Runtime annotations on method parameters. +@main def Test = + def check(actual: Int)(expect: Int)(msg: => String): Unit = + assert(actual == expect, s"$msg expected $expect but actually $actual") + locally: + // def bar[T](a: String, @Foo v: Int)(@Foo b: T, @Blah w: Int) + val method = classOf[Bar].getMethod("bar", classOf[String], classOf[Int], classOf[Object], classOf[Int]) + val annots = method.getParameterAnnotations() + check(annots.length)(4)("param count") + check(annots(0).length)(0)("count at 0") + check(annots(1).length)(1)("count at 1") + assert(annots(1)(0).isInstanceOf[Foo], "expect Foo at 1") + check(annots(2).length)(1)("count at 2") + assert(annots(2)(0).isInstanceOf[Foo], "expect Foo at 2") + check(annots(3).length)(0)("count at 3") + + locally: + // def bar2(@Foo v: Int) + val method = classOf[Bar].getMethod("bar2", classOf[Int]) + val annots = method.getParameterAnnotations() + check(annots.length)(1)("param count") + assert(annots(0)(0).isInstanceOf[Foo], "expect Foo at 0") + + locally: + // extension [T](s: String) def f[U](@Foo t: T, @Foo u: U) = () + val method = classOf[Bar].getMethod("f", classOf[String], classOf[Object], classOf[Object]) + val annots = method.getParameterAnnotations() + check(annots.length)(3)("param count") + check(annots(0).length)(0)("count at 0") + check(annots(1).length)(1)("count at 1") + check(annots(2).length)(1)("count at 2") + assert(annots(1)(0).isInstanceOf[Foo], "expect Foo at 1") + assert(annots(2)(0).isInstanceOf[Foo], "expect Foo at 2")