Skip to content

Commit 4db697b

Browse files
committed
Fix #8599: Emit trait init methods only as static methods.
Concrete trait methods are encoded as two methods in the back-end: * a default method containing the real body, and * a static forwarder, to use for super calls (due to JVM reasons). Previously, we did that also for the trait initializer methods `$init$`. However, that causes unrelated default methods to be inherited by Scala classes that extend several traits. In turn, that prevents Java classes from extending such classes. Now, for the `$init$` methods, instead of creating a static forwarder, we *move* the entire body to a static method. Therefore, we only create a static method, and no default method. This corresponds to what scalac does as well (both what we do and how we do it), although the previous "discrepancy" was not causing any incompatibility between Scala 2 and 3 per se.
1 parent 1a4106f commit 4db697b

File tree

6 files changed

+71
-25
lines changed

6 files changed

+71
-25
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import dotty.tools.dotc.core.Decorators._
1717
import dotty.tools.dotc.core.Flags._
1818
import dotty.tools.dotc.core.StdNames._
1919
import dotty.tools.dotc.core.NameKinds._
20+
import dotty.tools.dotc.core.Names.TermName
2021
import dotty.tools.dotc.core.Symbols._
2122
import dotty.tools.dotc.core.Types._
2223
import dotty.tools.dotc.core.Contexts._
@@ -577,6 +578,13 @@ trait BCodeSkelBuilder extends BCodeHelpers {
577578
* trait method. This is required for super calls to this method, which
578579
* go through the static forwarder in order to work around limitations
579580
* of the JVM.
581+
*
582+
* For the $init$ method, we must not leave it as a default method, but
583+
* instead we must put the whole body in the static method. If we leave
584+
* it as a default method, Java classes cannot extend Scala classes that
585+
* extend several Scala traits, since they then inherit unrelated default
586+
* $init$ methods. See #8599. scalac does the same thing.
587+
*
580588
* In theory, this would go in a separate MiniPhase, but it would have to
581589
* sit in a MegaPhase of its own between GenSJSIR and GenBCode, so the cost
582590
* is not worth it. We directly do it in this back-end instead, which also
@@ -586,9 +594,13 @@ trait BCodeSkelBuilder extends BCodeHelpers {
586594
val needsStaticImplMethod =
587595
claszSymbol.isInterface && !dd.rhs.isEmpty && !sym.isPrivate && !sym.isStaticMember
588596
if needsStaticImplMethod then
589-
genStaticForwarderForDefDef(dd)
590-
591-
genDefDef(dd)
597+
if sym.name == nme.TRAIT_CONSTRUCTOR then
598+
genTraitConstructorDefDef(dd)
599+
else
600+
genStaticForwarderForDefDef(dd)
601+
genDefDef(dd)
602+
else
603+
genDefDef(dd)
592604

593605
case tree: Template =>
594606
val body =
@@ -629,6 +641,26 @@ trait BCodeSkelBuilder extends BCodeHelpers {
629641

630642
} // end of method initJMethod
631643

644+
private def genTraitConstructorDefDef(dd: DefDef): Unit =
645+
val statifiedDef = makeStatifiedDefDef(dd)
646+
genDefDef(statifiedDef)
647+
648+
private def makeStatifiedDefDef(dd: DefDef): DefDef =
649+
val origSym = dd.symbol.asTerm
650+
val newSym = makeStatifiedDefSymbol(origSym, origSym.name)
651+
tpd.DefDef(newSym, { paramRefss =>
652+
val selfParamRef :: regularParamRefs = paramRefss.head
653+
val enclosingClass = origSym.owner.asClass
654+
new TreeTypeMap(
655+
typeMap = _.substThis(enclosingClass, selfParamRef.symbol.termRef)
656+
.subst(dd.vparamss.head.map(_.symbol), regularParamRefs.map(_.symbol.termRef)),
657+
treeMap = {
658+
case tree: This if tree.symbol == enclosingClass => selfParamRef
659+
case tree => tree
660+
}
661+
).transform(dd.rhs).changeOwner(origSym, newSym)
662+
})
663+
632664
private def genStaticForwarderForDefDef(dd: DefDef): Unit =
633665
val forwarderDef = makeStaticForwarder(dd)
634666
genDefDef(forwarderDef)
@@ -646,20 +678,23 @@ trait BCodeSkelBuilder extends BCodeHelpers {
646678
*/
647679
private def makeStaticForwarder(dd: DefDef): DefDef =
648680
val origSym = dd.symbol.asTerm
649-
val name = traitSuperAccessorName(origSym)
681+
val name = traitSuperAccessorName(origSym).toTermName
682+
val sym = makeStatifiedDefSymbol(origSym, name)
683+
tpd.DefDef(sym, { paramss =>
684+
val params = paramss.head
685+
tpd.Apply(params.head.select(origSym), params.tail)
686+
.withAttachment(BCodeHelpers.UseInvokeSpecial, ())
687+
})
688+
689+
private def makeStatifiedDefSymbol(origSym: TermSymbol, name: TermName): TermSymbol =
650690
val info = origSym.info match
651691
case mt: MethodType =>
652692
MethodType(nme.SELF :: mt.paramNames, origSym.owner.typeRef :: mt.paramInfos, mt.resType)
653-
val sym = origSym.copy(
693+
origSym.copy(
654694
name = name.toTermName,
655695
flags = Method | JavaStatic,
656696
info = info
657-
)
658-
tpd.DefDef(sym.asTerm, { paramss =>
659-
val params = paramss.head
660-
tpd.Apply(params.head.select(origSym), params.tail)
661-
.withAttachment(BCodeHelpers.UseInvokeSpecial, ())
662-
})
697+
).asTerm
663698

664699
def genDefDef(dd: DefDef): Unit = {
665700
val rhs = dd.rhs

tests/run/i8599/JavaClass_2.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class JavaClass_2 extends ScalaClass {
2+
public int c() {
3+
return a() + b();
4+
}
5+
}

tests/run/i8599/ScalaDefs_1.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait A {
2+
val a: Int = 1
3+
}
4+
5+
trait B {
6+
val b: Int = 2
7+
}
8+
9+
class ScalaClass extends A with B

tests/run/i8599/Test_3.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test {
2+
def main(args: Array[String]): Unit =
3+
val obj = new JavaClass_2
4+
assert(obj.a == 1)
5+
assert(obj.b == 2)
6+
assert(obj.c() == 3)
7+
}

tests/run/junitForwarders/C_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ object Test extends App {
1818
}
1919
check(classOf[C], "foo - @org.junit.Test()")
2020
// scala/scala-dev#213, scala/scala#5570: `foo$` should not have the @Test annotation
21-
check(classOf[T], "$init$ - ;$init$ - ;foo - @org.junit.Test();foo$ - ")
21+
check(classOf[T], "$init$ - ;foo - @org.junit.Test();foo$ - ")
2222
check(classOf[U], "bar - @org.junit.Test();bar$ - ")
2323
}

tests/run/traitNoInit.scala

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,11 @@ trait WithInit {
99

1010
trait Bar(x: Int)
1111

12-
class NoInitClass extends NoInit() with Bar(1) {
13-
def meth(x: Int) = x
14-
}
15-
16-
class WithInitClass extends WithInit() with Bar(1) {
17-
def meth(x: Int) = x
18-
}
19-
2012
object Test {
2113
def hasInit(cls: Class[_]) = cls.getMethods.map(_.toString).exists(_.contains("$init$"))
2214
def main(args: Array[String]): Unit = {
23-
val noInit = new NoInitClass {}
24-
val withInit = new WithInitClass {}
25-
26-
assert(!hasInit(noInit.getClass))
27-
assert(hasInit(withInit.getClass))
15+
assert(!hasInit(classOf[NoInit]))
16+
assert(hasInit(classOf[WithInit]))
17+
assert(!hasInit(classOf[Bar]))
2818
}
2919
}

0 commit comments

Comments
 (0)