Skip to content

Commit e73fb8a

Browse files
authored
Merge pull request #10509 from dotty-staging/trait-init-only-static
Fix #8599: Emit trait init methods only as static methods.
2 parents 2b72c18 + dec6cc6 commit e73fb8a

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)