From d31094fc9270f1788484aa06f08d3395d3d0eb14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 5 Nov 2024 10:39:17 +0100 Subject: [PATCH 1/5] Revert "Don't emit mixin forwarders as final" This reverts commit 700eea75d184ed37410bf5d4a4146ff2b1099829. Forward port of https://github.com/scala/scala/pull/8037/commits/96f844eb40430c3e0e0e738d24c39c0b002f3da1 --- .../tools/backend/jvm/BTypesFromSymbols.scala | 4 +--- tests/run/t11485.scala | 18 ------------------ 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 tests/run/t11485.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index 817d0be54d26..5eac15e309fc 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -298,9 +298,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAcce // without having to provide any implementations, but that is an // illegal combination of modifiers at the bytecode level so // suppress final if abstract if present. - && !sym.isOneOf(AbstractOrTrait) - // Mixin forwarders are bridges and can be final, but final bridges confuse some frameworks - && !sym.is(Bridge), ACC_FINAL) + && !sym.isOneOf(AbstractOrTrait), ACC_FINAL) .addFlagIf(sym.isStaticMember, ACC_STATIC) .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) diff --git a/tests/run/t11485.scala b/tests/run/t11485.scala deleted file mode 100644 index 3e339577b4a8..000000000000 --- a/tests/run/t11485.scala +++ /dev/null @@ -1,18 +0,0 @@ -// scalajs: --skip - -import java.lang.reflect.Modifier - -trait HaveFinalMethod { - final def finalMethod: String = "final" -} - -class Child extends HaveFinalMethod - -object Test { - def main(args: Array[String]): Unit = { - val meth = classOf[Child].getMethod("finalMethod") - assert(meth.isBridge) - val mods = meth.getModifiers - assert(!Modifier.isFinal(mods)) - } -} From a6d0f658517ee5301721e35bbc88939f84b9bbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 5 Nov 2024 14:22:53 +0100 Subject: [PATCH 2/5] Revert "Emit mixin forwarders as bridges" This reverts commit 6d0f9ca3ae4c7130c1ee18e0e21f283ff6cb21f3. Forward port of most commits in https://github.com/scala/scala/pull/8037 Compensate some of the consequences by adding the `MixedIn` flag. In addition to the use seen in the diff in `BCodeHelpers`, the JS backend has an existing test for `isOneOf(Bridge | MixedIn)` which needs this compensation. --- .../tools/backend/jvm/BCodeHelpers.scala | 7 ++---- .../dotty/tools/dotc/transform/Mixin.scala | 2 +- .../test/dotc/run-test-pickling.excludelist | 2 -- tests/run/mixin-bridge-methods.scala | 16 ++++++++++++++ tests/run/mixin-forwarder-overload/A.scala | 9 -------- tests/run/mixin-forwarder-overload/Test.java | 9 -------- tests/run/mixin-signatures.check | 14 ++++++------ tests/run/t3452b-bcode/J_2.java | 6 +++++ tests/run/t3452b-bcode/S_1.scala | 17 ++++++++++++++ tests/run/t3452b-bcode/S_3.scala | 7 ++++++ tests/run/t3452d/A.scala | 2 +- tests/run/t3452d/Test.java | 4 +++- tests/run/t3452g/A.scala | 4 +--- tests/run/t3452g/Test.java | 15 ++++++++----- tests/run/t3452h.scala | 17 ++++++++++---- tests/run/t8905/DoubleRDD.scala | 9 -------- tests/run/t8905/Test.java | 22 ------------------- 17 files changed, 83 insertions(+), 79 deletions(-) create mode 100644 tests/run/mixin-bridge-methods.scala delete mode 100644 tests/run/mixin-forwarder-overload/A.scala delete mode 100644 tests/run/mixin-forwarder-overload/Test.java create mode 100644 tests/run/t3452b-bcode/J_2.java create mode 100644 tests/run/t3452b-bcode/S_1.scala create mode 100644 tests/run/t3452b-bcode/S_3.scala delete mode 100644 tests/run/t8905/DoubleRDD.scala delete mode 100644 tests/run/t8905/Test.java diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index f8866f40d9d4..e37b8710ee54 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -491,7 +491,7 @@ trait BCodeHelpers extends BCodeIdiomatic { report.debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") for (m0 <- sortedMembersBasedOnFlags(moduleClass.info, required = Method, excluded = ExcludedForwarder)) { - val m = if (m0.is(Bridge)) m0.nextOverriddenSymbol else m0 + val m = if (m0.isOneOf(Bridge | MixedIn)) m0.nextOverriddenSymbol else m0 if (m == NoSymbol) report.log(s"$m0 is a bridge method that overrides nothing, something went wrong in a previous phase.") else if (m.isType || m.is(Deferred) || (m.owner eq defn.ObjectClass) || m.isConstructor || m.name.is(ExpandedName)) @@ -507,10 +507,7 @@ trait BCodeHelpers extends BCodeIdiomatic { // we generate ACC_SYNTHETIC forwarders so Java compilers ignore them. val isSynthetic = m0.name.is(NameKinds.SyntheticSetterName) || - // Only hide bridges generated at Erasure, mixin forwarders are also - // marked as bridge but shouldn't be hidden since they don't have a - // non-bridge overload. - m0.is(Bridge) && m0.initial.validFor.firstPhaseId == erasurePhase.next.id + m0.is(Bridge) addForwarder(jclass, moduleClass, m, isSynthetic) } } diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 9a19c0dc414f..105f471e79af 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -306,7 +306,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => for (meth <- mixin.info.decls.toList if needsMixinForwarder(meth)) yield { util.Stats.record("mixin forwarders") - transformFollowing(DefDef(mkForwarderSym(meth.asTerm, Bridge), forwarderRhsFn(meth))) + transformFollowing(DefDef(mkForwarderSym(meth.asTerm, extraFlags = MixedIn), forwarderRhsFn(meth))) } cpy.Template(impl)( diff --git a/compiler/test/dotc/run-test-pickling.excludelist b/compiler/test/dotc/run-test-pickling.excludelist index 1597487da668..e9c51a0d33cb 100644 --- a/compiler/test/dotc/run-test-pickling.excludelist +++ b/compiler/test/dotc/run-test-pickling.excludelist @@ -12,13 +12,11 @@ i9473.scala i13433.scala i13433b.scala macros-in-same-project1 -mixin-forwarder-overload t10889 t3452d t3452e t3452g t7374 -t8905 tuple-drop.scala tuple-ops.scala tuple-ops.scala diff --git a/tests/run/mixin-bridge-methods.scala b/tests/run/mixin-bridge-methods.scala new file mode 100644 index 000000000000..b14cd0caf150 --- /dev/null +++ b/tests/run/mixin-bridge-methods.scala @@ -0,0 +1,16 @@ +// scalajs: --skip + +trait Foo { + def getFoo() = "foo" +} + +class Sub extends Foo { + def getBar() = "bar" +} + +object Test { + def main(args: Array[String]): Unit = { + val ms = classOf[Sub].getDeclaredMethods + assert(ms forall (x => !x.isBridge), ms mkString " ") + } +} diff --git a/tests/run/mixin-forwarder-overload/A.scala b/tests/run/mixin-forwarder-overload/A.scala deleted file mode 100644 index 9eb20ac21414..000000000000 --- a/tests/run/mixin-forwarder-overload/A.scala +++ /dev/null @@ -1,9 +0,0 @@ -case class Foo(x: Int) - -trait A[X] { - def concat[Dummy](suffix: Int): Dummy = ??? -} - -class Bar extends A[Foo] { - def concat(suffix: Int): Foo = Foo(0) -} diff --git a/tests/run/mixin-forwarder-overload/Test.java b/tests/run/mixin-forwarder-overload/Test.java deleted file mode 100644 index 7bbd8dc9c7cb..000000000000 --- a/tests/run/mixin-forwarder-overload/Test.java +++ /dev/null @@ -1,9 +0,0 @@ -// scalajs: --skip - -public class Test { - public static void main(String[] args) { - Bar bar = new Bar(); - Foo x = bar.concat(0); - System.out.println(x); - } -} diff --git a/tests/run/mixin-signatures.check b/tests/run/mixin-signatures.check index 98979ab8d99b..77bff79ac8f1 100644 --- a/tests/run/mixin-signatures.check +++ b/tests/run/mixin-signatures.check @@ -1,19 +1,19 @@ class Test$bar1$ { + public java.lang.String Test$bar1$.f(java.lang.Object) public java.lang.Object Test$bar1$.f(java.lang.Object) - public java.lang.String Test$bar1$.f(java.lang.Object) public java.lang.String Test$bar1$.g(java.lang.String) public java.lang.Object Test$bar1$.g(java.lang.Object) public java.lang.String Test$bar1$.g(java.lang.Object) - public java.lang.Object Test$bar1$.h(java.lang.Object) + public java.lang.Object Test$bar1$.h(java.lang.Object) } class Test$bar2$ { + public java.lang.Object Test$bar2$.f(java.lang.String) public java.lang.Object Test$bar2$.f(java.lang.Object) - public java.lang.Object Test$bar2$.f(java.lang.String) public java.lang.String Test$bar2$.g(java.lang.String) public java.lang.Object Test$bar2$.g(java.lang.Object) public java.lang.Object Test$bar2$.g(java.lang.String) - public java.lang.Object Test$bar2$.h(java.lang.Object) + public java.lang.Object Test$bar2$.h(java.lang.Object) } class Test$bar3$ { @@ -23,7 +23,7 @@ class Test$bar3$ { public java.lang.String Test$bar3$.g(java.lang.String) public java.lang.Object Test$bar3$.g(java.lang.Object) public java.lang.String Test$bar3$.g(java.lang.Object) - public java.lang.Object Foo3.h(java.lang.Object) + public java.lang.Object Foo3.h(java.lang.Object) } class Test$bar4$ { @@ -33,7 +33,7 @@ class Test$bar4$ { public java.lang.String Test$bar4$.g(java.lang.String) public java.lang.Object Test$bar4$.g(java.lang.Object) public java.lang.Object Test$bar4$.g(java.lang.String) - public java.lang.Object Foo4.h(java.lang.Object) + public java.lang.Object Foo4.h(java.lang.Object) } class Test$bar5$ { @@ -45,7 +45,7 @@ class Test$bar5$ { public java.lang.Object Test$bar5$.g(java.lang.Object) public java.lang.Object Test$bar5$.g(java.lang.String) public java.lang.String Test$bar5$.g(java.lang.Object) - public java.lang.Object Test$bar5$.h(java.lang.Object) + public java.lang.Object Test$bar5$.h(java.lang.Object) } interface Foo1 { diff --git a/tests/run/t3452b-bcode/J_2.java b/tests/run/t3452b-bcode/J_2.java new file mode 100644 index 000000000000..839f334508e7 --- /dev/null +++ b/tests/run/t3452b-bcode/J_2.java @@ -0,0 +1,6 @@ +public class J_2 { + public static void j() { + StringSearch.search("test"); + StringSearch.searchC("test"); + } +} diff --git a/tests/run/t3452b-bcode/S_1.scala b/tests/run/t3452b-bcode/S_1.scala new file mode 100644 index 000000000000..a209f1203539 --- /dev/null +++ b/tests/run/t3452b-bcode/S_1.scala @@ -0,0 +1,17 @@ +trait Search[M] { + def search(input: M): C[Int] = { + println("Search received: " + input) + null + } +} + +class SearchC[M] { + def searchC(input: M): C[Int] = { + println("SearchC received: " + input) + null + } +} + +object StringSearch extends SearchC[String] with Search[String] + +trait C[T] diff --git a/tests/run/t3452b-bcode/S_3.scala b/tests/run/t3452b-bcode/S_3.scala new file mode 100644 index 000000000000..6373d39e9137 --- /dev/null +++ b/tests/run/t3452b-bcode/S_3.scala @@ -0,0 +1,7 @@ +// scalajs: --skip + +object Test { + def main(args: Array[String]): Unit = { + J_2.j() + } +} diff --git a/tests/run/t3452d/A.scala b/tests/run/t3452d/A.scala index fd88a98793ab..67a2080d273b 100644 --- a/tests/run/t3452d/A.scala +++ b/tests/run/t3452d/A.scala @@ -2,6 +2,6 @@ trait TraversableLike[A, Repr] { def tail: Repr = null.asInstanceOf[Repr] } -abstract class AbstractTrav[A] extends TraversableLike[A, Iterable[A]] +abstract class AbstractTrav[A] extends TraversableLike[A, Traversable[A]] class C[A] extends AbstractTrav[A] diff --git a/tests/run/t3452d/Test.java b/tests/run/t3452d/Test.java index 9ee02ed092a8..5760bc6460eb 100644 --- a/tests/run/t3452d/Test.java +++ b/tests/run/t3452d/Test.java @@ -3,6 +3,8 @@ public class Test { public static void main(String[] args) { C c = new C(); - scala.collection.Iterable ls = c.tail(); + // TODO add a bridge during mixin so we can expose + // sharper generic signature for `tail`. + /*Traversable*/ Object ls = c.tail(); } } diff --git a/tests/run/t3452g/A.scala b/tests/run/t3452g/A.scala index df151d5313b3..a3f74c1e1e4c 100644 --- a/tests/run/t3452g/A.scala +++ b/tests/run/t3452g/A.scala @@ -4,8 +4,6 @@ trait TraversableLike[A, Repr] { abstract class AbstractTrav[A] extends TraversableLike[A, AbstractTrav[A]] -class C1 extends AbstractTrav[String] - object O extends AbstractTrav[String] -class C2[A] extends AbstractTrav[A] +class C[A] extends AbstractTrav[A] diff --git a/tests/run/t3452g/Test.java b/tests/run/t3452g/Test.java index 594abc898ffe..8f4cf96e45c8 100644 --- a/tests/run/t3452g/Test.java +++ b/tests/run/t3452g/Test.java @@ -1,12 +1,15 @@ // scalajs: --skip public class Test { - public static void main(String[] args) { - AbstractTrav lsSharp1 = new C1().tail(); + public static void main(String[] args) { + // To get better types here, we would need to + // add bridge during mixin so we can expose + // a generic return type of Traversable, because the erasure + // of this (Traversable) differs from the erasure of the mixed + // method (erasure(Repr) = Object) - // Object is the result type for the static forwarder (might be because of #11305) - Object lsSharp2 = O.tail(); + Object lsSharp = O.tail(); - AbstractTrav lsSharp3 = new C2().tail(); - } + Object lsSharp2 = new C().tail(); + } } diff --git a/tests/run/t3452h.scala b/tests/run/t3452h.scala index 6237d3ea641a..5ccc8aecc10e 100644 --- a/tests/run/t3452h.scala +++ b/tests/run/t3452h.scala @@ -1,8 +1,17 @@ -class Mix___eFoo_I_wBar__f extends Foo_I_ with Bar__f { f; } +class Mix extends Foo with Bar { f; } trait T -abstract class Foo_I_ { class I extends T ; def f: I ; f; } -trait Bar__f { type I>:Null<:T; def f: I = {null}; f; def gobble: I = {null}} +abstract class Foo { + class I extends T + def f: I + f +} +trait Bar { + type I >: Null <: T + def f: I = null + f + def gobble: I = null +} object Test extends App { - new Mix___eFoo_I_wBar__f + new Mix } diff --git a/tests/run/t8905/DoubleRDD.scala b/tests/run/t8905/DoubleRDD.scala deleted file mode 100644 index 65e993ffe02b..000000000000 --- a/tests/run/t8905/DoubleRDD.scala +++ /dev/null @@ -1,9 +0,0 @@ -import java.util.Comparator - -trait RDDLike[T] { - def max(comp: Comparator[T]): T = { - (1.0).asInstanceOf[T] - } -} - -class DoubleRDD extends RDDLike[java.lang.Double] { } diff --git a/tests/run/t8905/Test.java b/tests/run/t8905/Test.java deleted file mode 100644 index e327beb3c819..000000000000 --- a/tests/run/t8905/Test.java +++ /dev/null @@ -1,22 +0,0 @@ -// scalajs: --skip - -import java.util.Comparator; - -public class Test { - private static class DoubleComparator implements Comparator { - public int compare(Double o1, Double o2) { - return o1.compareTo(o2); - } - } - - public static void main(String[] args) { - DoubleRDD rdd = new DoubleRDD(); - RDDLike rddLike = rdd; - - // This call works fine: - double rddLikeMax = rddLike.max(new DoubleComparator()); - // In Scala 2.10.4, this code compiles but this call fails at runtime: - // java.lang.NoSuchMethodError: DoubleRDD.max(Ljava/util/Comparator;)Ljava/lang/Double; - double rddMax = rdd.max(new DoubleComparator()); - } -} From 75cd85ebe266ab80a9fcbde045c20e0204e03b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 5 Nov 2024 15:07:46 +0100 Subject: [PATCH 3/5] Add test case for #19270. Closes #19270. --- tests/run/i19270.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/run/i19270.scala diff --git a/tests/run/i19270.scala b/tests/run/i19270.scala new file mode 100644 index 000000000000..128e5c108f94 --- /dev/null +++ b/tests/run/i19270.scala @@ -0,0 +1,17 @@ +// scalajs: --skip + +trait T { + def foo(x: Int): Int = x + 1 +} + +class C extends T + +object Test { + def main(args: Array[String]): Unit = { + println("i19270") + val m = classOf[C].getDeclaredMethod("foo", classOf[Int]) + assert(m.getDeclaringClass() == classOf[C], m.getDeclaringClass()) + assert(!m.isBridge(), "foo should not have the ACC_BRIDGE flag") + assert(!m.isSynthetic(), "foo should not have the ACC_SYNTHETIC flag") + } +} From 59c4eadc87102e336cc8bc44934605dd77740f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 12 Nov 2024 15:16:46 +0100 Subject: [PATCH 4/5] Emit Java generic signatures for mixin forwarders. This is a forward port of https://github.com/scala/scala/pull/8037/commits/6fc2202a1e5a89067235b0abbc550e80085f7290 In Scala 2 that commit was reverting a commit that had stopped emitting Java generic signatures. In dotc we never had that logic before, so this is new code. --- .../tools/backend/jvm/BCodeHelpers.scala | 11 ++++++- .../src/dotty/tools/dotc/core/Phases.scala | 4 +++ .../dotty/tools/dotc/transform/Mixin.scala | 29 ++++++++++++++++++- tests/pos/11484/A_2.java | 1 + tests/pos/11484/C_1.scala | 6 ++++ tests/pos/11512/A_2.java | 1 + tests/pos/11512/C_1.scala | 7 +++++ .../ExtensionId_1.scala | 18 ++++++++++++ .../JavaExtension_2.java | 8 +++++ tests/run/t7932.check | 4 +-- 10 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 tests/pos/11484/A_2.java create mode 100644 tests/pos/11484/C_1.scala create mode 100644 tests/pos/11512/A_2.java create mode 100644 tests/pos/11512/C_1.scala create mode 100644 tests/pos/mixin-generic-extended-by-java/ExtensionId_1.scala create mode 100644 tests/pos/mixin-generic-extended-by-java/JavaExtension_2.java diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index e37b8710ee54..ab264fe4889d 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -31,6 +31,7 @@ import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.TypeErasure import dotty.tools.dotc.transform.GenericSignatures import dotty.tools.dotc.transform.ElimErasedValueType +import dotty.tools.dotc.transform.Mixin import dotty.tools.io.AbstractFile import dotty.tools.dotc.report @@ -395,12 +396,20 @@ trait BCodeHelpers extends BCodeIdiomatic { */ def getGenericSignature(sym: Symbol, owner: Symbol): String = { atPhase(erasurePhase) { - val memberTpe = + def computeMemberTpe(): Type = if (sym.is(Method)) sym.denot.info else if sym.denot.validFor.phaseId > erasurePhase.id && sym.isField && sym.getter.exists then // Memoization field of getter entered after erasure, see run/i17069 for an example sym.getter.denot.info.resultType else owner.denot.thisType.memberInfo(sym) + + val memberTpe = if sym.is(MixedIn) then + mixinPhase.asInstanceOf[Mixin].mixinForwarderGenericInfos.get(sym) match + case Some(genericInfo) => genericInfo + case none => computeMemberTpe() + else + computeMemberTpe() + getGenericSignatureHelper(sym, owner, memberTpe).orNull } } diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index e3351628e43e..9400a3bad18c 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -239,6 +239,7 @@ object Phases { private var myErasurePhase: Phase = uninitialized private var myElimErasedValueTypePhase: Phase = uninitialized private var myLambdaLiftPhase: Phase = uninitialized + private var myMixinPhase: Phase = uninitialized private var myCountOuterAccessesPhase: Phase = uninitialized private var myFlattenPhase: Phase = uninitialized private var myGenBCodePhase: Phase = uninitialized @@ -266,6 +267,7 @@ object Phases { final def gettersPhase: Phase = myGettersPhase final def erasurePhase: Phase = myErasurePhase final def elimErasedValueTypePhase: Phase = myElimErasedValueTypePhase + final def mixinPhase: Phase = myMixinPhase final def lambdaLiftPhase: Phase = myLambdaLiftPhase final def countOuterAccessesPhase = myCountOuterAccessesPhase final def flattenPhase: Phase = myFlattenPhase @@ -295,6 +297,7 @@ object Phases { myErasurePhase = phaseOfClass(classOf[Erasure]) myElimErasedValueTypePhase = phaseOfClass(classOf[ElimErasedValueType]) myPatmatPhase = phaseOfClass(classOf[PatternMatcher]) + myMixinPhase = phaseOfClass(classOf[Mixin]) myLambdaLiftPhase = phaseOfClass(classOf[LambdaLift]) myCountOuterAccessesPhase = phaseOfClass(classOf[CountOuterAccesses]) myFlattenPhase = phaseOfClass(classOf[Flatten]) @@ -551,6 +554,7 @@ object Phases { def gettersPhase(using Context): Phase = ctx.base.gettersPhase def erasurePhase(using Context): Phase = ctx.base.erasurePhase def elimErasedValueTypePhase(using Context): Phase = ctx.base.elimErasedValueTypePhase + def mixinPhase(using Context): Phase = ctx.base.mixinPhase def lambdaLiftPhase(using Context): Phase = ctx.base.lambdaLiftPhase def flattenPhase(using Context): Phase = ctx.base.flattenPhase def genBCodePhase(using Context): Phase = ctx.base.genBCodePhase diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 105f471e79af..558cd76fa44e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -16,6 +16,7 @@ import StdNames.* import Names.* import NameKinds.* import NameOps.* +import Phases.erasurePhase import ast.Trees.* import dotty.tools.dotc.transform.sjs.JSSymUtils.isJSType @@ -115,6 +116,15 @@ object Mixin { class Mixin extends MiniPhase with SymTransformer { thisPhase => import ast.tpd.* + /** Infos before erasure of the generated mixin forwarders. + * + * These will be used to generate Java generic signatures of the mixin + * forwarders. Normally we use the types before erasure; we cannot do that + * for mixin forwarders since they are created after erasure, and therefore + * their type history does not have anything recorded for before erasure. + */ + val mixinForwarderGenericInfos = MutableSymbolMap[Type]() + override def phaseName: String = Mixin.name override def description: String = Mixin.description @@ -306,8 +316,25 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => for (meth <- mixin.info.decls.toList if needsMixinForwarder(meth)) yield { util.Stats.record("mixin forwarders") - transformFollowing(DefDef(mkForwarderSym(meth.asTerm, extraFlags = MixedIn), forwarderRhsFn(meth))) + transformFollowing(DefDef(mkMixinForwarderSym(meth.asTerm), forwarderRhsFn(meth))) + } + + def mkMixinForwarderSym(target: TermSymbol): TermSymbol = + val sym = mkForwarderSym(target, extraFlags = MixedIn) + val (infoBeforeErasure, isDifferentThanInfoNow) = atPhase(erasurePhase) { + val beforeErasure = cls.thisType.memberInfo(target) + (beforeErasure, !(beforeErasure =:= sym.info)) } + if isDifferentThanInfoNow then + // The info before erasure would not have been the same as the info now. + // We want to store it for the backend to compute the generic Java signature. + // However, we must still avoid doing that if erasing that signature would + // not give the same erased type. If it doesn't, we'll just give a completely + // incorrect Java signature. (This could be improved by generating dedicated + // bridges, but we don't go that far; scalac doesn't either.) + if TypeErasure.transformInfo(target, infoBeforeErasure) =:= sym.info then + mixinForwarderGenericInfos(sym) = infoBeforeErasure + sym cpy.Template(impl)( constr = diff --git a/tests/pos/11484/A_2.java b/tests/pos/11484/A_2.java new file mode 100644 index 000000000000..aa8ef2cf5a42 --- /dev/null +++ b/tests/pos/11484/A_2.java @@ -0,0 +1 @@ +public class A_2 extends C { } diff --git a/tests/pos/11484/C_1.scala b/tests/pos/11484/C_1.scala new file mode 100644 index 000000000000..48f5bd8174cf --- /dev/null +++ b/tests/pos/11484/C_1.scala @@ -0,0 +1,6 @@ +class B[A] +sealed trait T[A] { + def overloaded(that: List[T[A]]): T[A] = that.head + def overloaded(that: List[B[A]]): B[A] = that.head +} +abstract class C[A] extends T[A] diff --git a/tests/pos/11512/A_2.java b/tests/pos/11512/A_2.java new file mode 100644 index 000000000000..ed549568a5f4 --- /dev/null +++ b/tests/pos/11512/A_2.java @@ -0,0 +1 @@ +public class A_2 extends C { } diff --git a/tests/pos/11512/C_1.scala b/tests/pos/11512/C_1.scala new file mode 100644 index 000000000000..8e791e333a82 --- /dev/null +++ b/tests/pos/11512/C_1.scala @@ -0,0 +1,7 @@ +trait T { this: U => + def m: Int +} +trait U { + def m: Int = ??? +} +abstract class C extends U with T diff --git a/tests/pos/mixin-generic-extended-by-java/ExtensionId_1.scala b/tests/pos/mixin-generic-extended-by-java/ExtensionId_1.scala new file mode 100644 index 000000000000..f54f39879ac4 --- /dev/null +++ b/tests/pos/mixin-generic-extended-by-java/ExtensionId_1.scala @@ -0,0 +1,18 @@ +trait Extension + +class ClassicActorSystemProvider + +/** + * Identifies an Extension + * Lookup of Extensions is done by object identity, so the Id must be the same wherever it's used, + * otherwise you'll get the same extension loaded multiple times. + */ +trait ExtensionId[T <: Extension] { + + def get(system: ClassicActorSystemProvider): T = ??? +} + +/** + * Java API for ExtensionId + */ +abstract class AbstractExtensionId[T <: Extension] extends ExtensionId[T] diff --git a/tests/pos/mixin-generic-extended-by-java/JavaExtension_2.java b/tests/pos/mixin-generic-extended-by-java/JavaExtension_2.java new file mode 100644 index 000000000000..1711379d039e --- /dev/null +++ b/tests/pos/mixin-generic-extended-by-java/JavaExtension_2.java @@ -0,0 +1,8 @@ + +public class JavaExtension_2 { + static class TestExtensionId extends AbstractExtensionId { + } + + static class TestExtension implements Extension { + } +} diff --git a/tests/run/t7932.check b/tests/run/t7932.check index 5ba7ec1a3216..b7e516d73a41 100644 --- a/tests/run/t7932.check +++ b/tests/run/t7932.check @@ -1,5 +1,5 @@ -public Category C.category() -public Category C.category1() +public Category C.category() +public Category C.category1() public abstract Category M2.category3() public abstract Category M2.category2() public default Category M1.category() From 66f90c65982a2b9ee413ca26366e189d9236bc33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 25 Feb 2025 13:55:02 +0100 Subject: [PATCH 5/5] Mixins were not the only bridges. Bridges must actually not be final. --- .../tools/backend/jvm/BTypesFromSymbols.scala | 4 +++- tests/run/mixin-final-def-object-lucre.scala | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/run/mixin-final-def-object-lucre.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index 5eac15e309fc..68c6add4ef13 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -298,7 +298,9 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAcce // without having to provide any implementations, but that is an // illegal combination of modifiers at the bytecode level so // suppress final if abstract if present. - && !sym.isOneOf(AbstractOrTrait), ACC_FINAL) + && !sym.isOneOf(AbstractOrTrait) + // Bridges can be final, but final bridges confuse some frameworks + && !sym.is(Bridge), ACC_FINAL) .addFlagIf(sym.isStaticMember, ACC_STATIC) .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) diff --git a/tests/run/mixin-final-def-object-lucre.scala b/tests/run/mixin-final-def-object-lucre.scala new file mode 100644 index 000000000000..676efb46c977 --- /dev/null +++ b/tests/run/mixin-final-def-object-lucre.scala @@ -0,0 +1,19 @@ +trait EventLike + +trait GrandParent: + def changed: EventLike + +trait HasChanged extends GrandParent: + override def changed: EventLike + +abstract class Parent extends GrandParent: + object changed extends EventLike + +class Child extends Parent with HasChanged + +object Test: + def main(args: Array[String]): Unit = + val child = Child() + println(child.changed) + println((child: HasChanged).changed) +end Test