Skip to content

Commit 5bf664d

Browse files
committed
Fix scala#1441: init $MODULE in <clinit>
This is a port of the following PR from Scala 2: scala/scala#7270
1 parent 917599f commit 5bf664d

File tree

3 files changed

+85
-24
lines changed

3 files changed

+85
-24
lines changed

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

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import scala.tools.asm.util.{TraceMethodVisitor, ASMifier}
1010
import java.io.PrintWriter
1111

1212
import dotty.tools.dotc.ast.tpd
13+
import dotty.tools.dotc.ast.TreeTypeMap
1314
import dotty.tools.dotc.CompilationUnit
1415
import dotty.tools.dotc.core.Annotations.Annotation
1516
import dotty.tools.dotc.core.Decorators._
1617
import dotty.tools.dotc.core.Flags._
17-
import dotty.tools.dotc.core.StdNames.str
18+
import dotty.tools.dotc.core.StdNames._
1819
import dotty.tools.dotc.core.Symbols._
19-
import dotty.tools.dotc.core.Types.Type
20+
import dotty.tools.dotc.core.Types._
2021
import dotty.tools.dotc.util.Spans._
22+
import dotty.tools.dotc.transform.SymUtils._
2123

2224
/*
2325
*
@@ -92,12 +94,12 @@ trait BCodeSkelBuilder extends BCodeHelpers {
9294

9395
/* ---------------- helper utils for generating classes and fields ---------------- */
9496

95-
def genPlainClass(cd: TypeDef) = cd match {
96-
case TypeDef(_, impl) =>
97+
def genPlainClass(cd0: TypeDef) = cd0 match {
98+
case TypeDef(_, impl: Template) =>
9799
assert(cnode == null, "GenBCode detected nested methods.")
98100
innerClassBufferASM.clear()
99101

100-
claszSymbol = cd.symbol
102+
claszSymbol = cd0.symbol
101103
isCZParcelable = isAndroidParcelableClass(claszSymbol)
102104
isCZStaticModule = claszSymbol.isStaticModuleClass
103105
thisName = internalName(claszSymbol)
@@ -106,14 +108,70 @@ trait BCodeSkelBuilder extends BCodeHelpers {
106108

107109
initJClass(cnode)
108110

111+
val cd = if (isCZStaticModule) {
112+
// Move statements from the primary constructor following the superclass constructor call to
113+
// a newly synthesised tree representing the "<clinit>", which also assigns the MODULE$ field.
114+
// Because the assigments to both the module instance fields, and the fields of the module itself
115+
// are in the <clinit>, these fields can be static + final.
116+
117+
// TODO should we do this transformation earlier, say in Constructors? Or would that just cause
118+
// pain for scala-{js, native}?
119+
120+
for (f <- claszSymbol.info.decls.filter(_.isField))
121+
f.setFlag(JavaStatic)
122+
123+
val (uptoSuperStats, remainingConstrStats) = splitAtSuper(impl.constr.rhs.asInstanceOf[Block].stats)
124+
val clInitSymbol = ctx.newSymbol(
125+
claszSymbol,
126+
nme.STATIC_CONSTRUCTOR,
127+
JavaStatic | Method,
128+
MethodType(Nil)(_ => Nil, _ => defn.UnitType),
129+
privateWithin = NoSymbol,
130+
coord = claszSymbol.coord
131+
)
132+
133+
// We don't need to enter this field into the decls of claszSymbol.info as this is added manually to the generated class
134+
// in addModuleInstanceField. TODO: try adding it to the decls and making the usual field generation do the right thing.
135+
val moduleField = ctx.newSymbol(
136+
claszSymbol,
137+
str.MODULE_INSTANCE_FIELD.toTermName,
138+
JavaStatic | Private,
139+
claszSymbol.typeRef,
140+
privateWithin = NoSymbol,
141+
coord = claszSymbol.coord
142+
)
143+
144+
val thisMap = new TreeTypeMap(
145+
treeMap = {
146+
case tree: This if tree.symbol == claszSymbol =>
147+
ref(claszSymbol.sourceModule)
148+
case tree =>
149+
tree
150+
},
151+
oldOwners = claszSymbol.primaryConstructor :: Nil,
152+
newOwners = clInitSymbol :: Nil
153+
)
154+
155+
val callConstructor = New(claszSymbol.typeRef).select(claszSymbol.primaryConstructor).appliedToArgs(Nil)
156+
val assignModuleField = Assign(ref(moduleField), callConstructor)
157+
val remainingConstrStatsSubst = remainingConstrStats.map(thisMap(_))
158+
val clinit = DefDef(
159+
clInitSymbol,
160+
Block(assignModuleField :: remainingConstrStatsSubst, unitLiteral)
161+
)
162+
163+
val constr2 = {
164+
val rhs = Block(uptoSuperStats, impl.constr.rhs.asInstanceOf[Block].expr)
165+
cpy.DefDef(impl.constr)(rhs = rhs)
166+
}
167+
168+
val impl2 = cpy.Template(impl)(constr = constr2, body = clinit :: impl.body)
169+
cpy.TypeDef(cd0)(rhs = impl2)
170+
} else cd0
171+
109172
val methodSymbols = for (f <- cd.symbol.info.decls.toList if f.is(Method) && f.isTerm && !f.is(Module)) yield f
110173
val hasStaticCtor = methodSymbols exists (_.isStaticConstructor)
111-
if (!hasStaticCtor) {
112-
// but needs one ...
113-
if (isCZStaticModule || isCZParcelable) {
114-
fabricateStaticInit()
115-
}
116-
}
174+
if (!hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid()
117175

118176
val optSerial: Option[Long] =
119177
claszSymbol.getAnnotation(defn.SerialVersionUIDAnnot).flatMap { annot =>
@@ -222,7 +280,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
222280
/*
223281
* must-single-thread
224282
*/
225-
private def fabricateStaticInit(): Unit = {
283+
private def fabricateStaticInitAndroid(): Unit = {
226284

227285
val clinit: asm.MethodVisitor = cnode.visitMethod(
228286
GenBCodeOps.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
@@ -233,15 +291,9 @@ trait BCodeSkelBuilder extends BCodeHelpers {
233291
)
234292
clinit.visitCode()
235293

236-
/* "legacy static initialization" */
237-
if (isCZStaticModule) {
238-
clinit.visitTypeInsn(asm.Opcodes.NEW, thisName)
239-
clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL,
240-
thisName, INSTANCE_CONSTRUCTOR_NAME, "()V", false)
241-
}
242294
if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) }
243-
clinit.visitInsn(asm.Opcodes.RETURN)
244295

296+
clinit.visitInsn(asm.Opcodes.RETURN)
245297
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
246298
clinit.visitEnd()
247299
}
@@ -631,7 +683,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
631683
/*
632684
* must-single-thread
633685
*
634-
* TODO document, explain interplay with `fabricateStaticInit()`
686+
* TODO document, explain interplay with `fabricateStaticInitAndroid()`
635687
*/
636688
private def appendToStaticCtor(dd: DefDef): Unit = {
637689

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,18 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
811811
loop(tree)
812812
}
813813

814+
/** Return a pair consisting of (supercall, rest)
815+
*
816+
* - supercall: the of superclass call, excluding trait constr calls
817+
*
818+
* The supercall is always the first statement (if exists)
819+
*/
820+
final def splitAtSuper(constrStats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) =
821+
constrStats.toList match {
822+
case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest)
823+
case stats => (Nil, stats)
824+
}
825+
814826
/** Structural tree comparison (since == on trees is reference equality).
815827
* For the moment, only Ident, Select, Literal, Apply and TypeApply are supported
816828
*/

compiler/src/dotty/tools/dotc/transform/Constructors.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
257257
// TODO: this happens to work only because Constructors is the last phase in group
258258
}
259259

260-
val (superCalls, followConstrStats) = constrStats.toList match {
261-
case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest)
262-
case stats => (Nil, stats)
263-
}
260+
val (superCalls, followConstrStats) = splitAtSuper(constrStats.toList)
264261

265262
val mappedSuperCalls = vparams match {
266263
case (outerParam @ ValDef(nme.OUTER, _, _)) :: _ =>

0 commit comments

Comments
 (0)