From 00e257d8798c39474f733b8b77b662d8469af0bb Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Wed, 6 Aug 2025 15:21:40 +0200 Subject: [PATCH] Do not generate certain interface mixin forwarders if not necessary This affects indirectly extended java interfaces, bringing the behavior closer to how scala 2 handles it. After this commit, the methods from those interfaces will not be generated, if they are unnecessary (e.g., where there is no conflict between parents when calling that method) even if `-Xmixin-force-forwarders` is set to true. Those previously added mixin forwarders caused meant that since specific parents were referenced in those methods, they had to be added to the `interfaces` array in the generated classfile (without being added to the signature, which caused an issue with getInterfaces and getGenericInterfaces not being equal). --- .../src/dotty/tools/dotc/transform/MixinOps.scala | 9 ++++++++- tests/run/i21177a/BaseRootJava.java | 4 ++++ tests/run/i21177a/GenericBaseIntermidiateJava.java | 2 ++ tests/run/i21177a/Test.scala | 12 ++++++++++++ tests/run/i21177b/A.java | 1 + tests/run/i21177b/Test.scala | 11 +++++++++++ 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/run/i21177a/BaseRootJava.java create mode 100644 tests/run/i21177a/GenericBaseIntermidiateJava.java create mode 100644 tests/run/i21177a/Test.scala create mode 100644 tests/run/i21177b/A.java create mode 100644 tests/run/i21177b/Test.scala diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index 1b2d3e79c9a4..3a23c2757760 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -67,9 +67,16 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(using Context) { def generateSerializationForwarder: Boolean = (meth.name == nme.readResolve || meth.name == nme.writeReplace) && meth.info.paramNamess.flatten.isEmpty + // Even when mixinForwarderChoices option is true, we might not want to generate certain + // mixin forwarders to avoid discrepancies between java getInterfaces() and getGenericInterfaces(), + // as adding some unnecessary mixin forwarders forces us to extend the `interfaces` array + // of the generated class in its classfile + def isJavaInterfaceIndirectlyExtended = + meth.owner.isAllOf(Flags.JavaInterface) && !cls.parentSyms.contains(meth.owner) + !meth.isConstructor && meth.is(Method, butNot = PrivateOrAccessorOrDeferred) && - (ctx.settings.mixinForwarderChoices.isTruthy || meth.owner.is(Scala2x) || needsDisambiguation || hasNonInterfaceDefinition || + ((ctx.settings.mixinForwarderChoices.isTruthy && !isJavaInterfaceIndirectlyExtended)|| meth.owner.is(Scala2x) || needsDisambiguation || hasNonInterfaceDefinition || generateJUnitForwarder || generateSerializationForwarder) && isInImplementingClass(meth) } diff --git a/tests/run/i21177a/BaseRootJava.java b/tests/run/i21177a/BaseRootJava.java new file mode 100644 index 000000000000..efdb5131f94c --- /dev/null +++ b/tests/run/i21177a/BaseRootJava.java @@ -0,0 +1,4 @@ +public interface BaseRootJava { + default void someMethod() { + } +} diff --git a/tests/run/i21177a/GenericBaseIntermidiateJava.java b/tests/run/i21177a/GenericBaseIntermidiateJava.java new file mode 100644 index 000000000000..6b39357272a6 --- /dev/null +++ b/tests/run/i21177a/GenericBaseIntermidiateJava.java @@ -0,0 +1,2 @@ +public interface GenericBaseIntermidiateJava extends BaseRootJava { +} \ No newline at end of file diff --git a/tests/run/i21177a/Test.scala b/tests/run/i21177a/Test.scala new file mode 100644 index 000000000000..2d0fd858a9be --- /dev/null +++ b/tests/run/i21177a/Test.scala @@ -0,0 +1,12 @@ +class ChildScala extends GenericBaseIntermidiateJava[Int] { + // override def someMethod() = ??? +} +object Test { + def main(args: Array[String]): Unit = { + val c = classOf[ChildScala] + assert( + c.getGenericInterfaces.length == c.getInterfaces.length, + s"mismatch between ${c.getGenericInterfaces.mkString("Array(", ", ", ")")} and ${c.getInterfaces.mkString("Array(", ", ", ")")}" + ) + } +} \ No newline at end of file diff --git a/tests/run/i21177b/A.java b/tests/run/i21177b/A.java new file mode 100644 index 000000000000..c7ff0592cf9f --- /dev/null +++ b/tests/run/i21177b/A.java @@ -0,0 +1 @@ +interface A { default int m() { return 1; } } \ No newline at end of file diff --git a/tests/run/i21177b/Test.scala b/tests/run/i21177b/Test.scala new file mode 100644 index 000000000000..9054ac6d5f74 --- /dev/null +++ b/tests/run/i21177b/Test.scala @@ -0,0 +1,11 @@ +abstract class B { def m(): Int } +trait T extends B with A +class C extends T + +object Test { + def main(args: Array[String]): Unit = { + val cls = classOf[C] + assert(cls.getInterfaces().length == cls.getGenericInterfaces().length) + new C().m() // lack of the mixin forwarder for m() will crash this on runtime + } +}