Skip to content

Commit dec6cc6

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 2efaeb0 commit dec6cc6

File tree

6 files changed

+87
-25
lines changed

6 files changed

+87
-25
lines changed

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

Lines changed: 62 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,42 @@ 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+
/** Creates a copy of the given DefDef that is static and where an explicit
649+
* self parameter represents the original `this` value.
650+
*
651+
* Example: from
652+
* {{{
653+
* trait Enclosing {
654+
* def foo(x: Int): String = this.toString() + x
655+
* }
656+
* }}}
657+
* the statified version of `foo` would be
658+
* {{{
659+
* static def foo($self: Enclosing, x: Int): String = $self.toString() + x
660+
* }}}
661+
*/
662+
private def makeStatifiedDefDef(dd: DefDef): DefDef =
663+
val origSym = dd.symbol.asTerm
664+
val newSym = makeStatifiedDefSymbol(origSym, origSym.name)
665+
tpd.DefDef(newSym, { paramRefss =>
666+
val selfParamRef :: regularParamRefs = paramRefss.head
667+
val enclosingClass = origSym.owner.asClass
668+
new TreeTypeMap(
669+
typeMap = _.substThis(enclosingClass, selfParamRef.symbol.termRef)
670+
.subst(dd.vparamss.head.map(_.symbol), regularParamRefs.map(_.symbol.termRef)),
671+
treeMap = {
672+
case tree: This if tree.symbol == enclosingClass => selfParamRef
673+
case tree => tree
674+
},
675+
oldOwners = origSym :: Nil,
676+
newOwners = newSym :: Nil
677+
).transform(dd.rhs)
678+
})
679+
632680
private def genStaticForwarderForDefDef(dd: DefDef): Unit =
633681
val forwarderDef = makeStaticForwarder(dd)
634682
genDefDef(forwarderDef)
@@ -646,20 +694,23 @@ trait BCodeSkelBuilder extends BCodeHelpers {
646694
*/
647695
private def makeStaticForwarder(dd: DefDef): DefDef =
648696
val origSym = dd.symbol.asTerm
649-
val name = traitSuperAccessorName(origSym)
697+
val name = traitSuperAccessorName(origSym).toTermName
698+
val sym = makeStatifiedDefSymbol(origSym, name)
699+
tpd.DefDef(sym, { paramss =>
700+
val params = paramss.head
701+
tpd.Apply(params.head.select(origSym), params.tail)
702+
.withAttachment(BCodeHelpers.UseInvokeSpecial, ())
703+
})
704+
705+
private def makeStatifiedDefSymbol(origSym: TermSymbol, name: TermName): TermSymbol =
650706
val info = origSym.info match
651707
case mt: MethodType =>
652708
MethodType(nme.SELF :: mt.paramNames, origSym.owner.typeRef :: mt.paramInfos, mt.resType)
653-
val sym = origSym.copy(
709+
origSym.copy(
654710
name = name.toTermName,
655711
flags = Method | JavaStatic,
656712
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-
})
713+
).asTerm
663714

664715
def genDefDef(dd: DefDef): Unit = {
665716
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)