Skip to content

Commit 4df1640

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 93e4416 commit 4df1640

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,15 +10,17 @@ 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._
2122
import dotty.tools.dotc.report
23+
import dotty.tools.dotc.transform.SymUtils._
2224

2325
/*
2426
*
@@ -93,12 +95,12 @@ trait BCodeSkelBuilder extends BCodeHelpers {
9395

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

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

101-
claszSymbol = cd.symbol
103+
claszSymbol = cd0.symbol
102104
isCZParcelable = isAndroidParcelableClass(claszSymbol)
103105
isCZStaticModule = claszSymbol.isStaticModuleClass
104106
thisName = internalName(claszSymbol)
@@ -107,14 +109,70 @@ trait BCodeSkelBuilder extends BCodeHelpers {
107109

108110
initJClass(cnode)
109111

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

119177
val optSerial: Option[Long] =
120178
claszSymbol.getAnnotation(defn.SerialVersionUIDAnnot).flatMap { annot =>
@@ -223,7 +281,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
223281
/*
224282
* must-single-thread
225283
*/
226-
private def fabricateStaticInit(): Unit = {
284+
private def fabricateStaticInitAndroid(): Unit = {
227285

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

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

297+
clinit.visitInsn(asm.Opcodes.RETURN)
246298
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
247299
clinit.visitEnd()
248300
}
@@ -632,7 +684,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
632684
/*
633685
* must-single-thread
634686
*
635-
* TODO document, explain interplay with `fabricateStaticInit()`
687+
* TODO document, explain interplay with `fabricateStaticInitAndroid()`
636688
*/
637689
private def appendToStaticCtor(dd: DefDef): Unit = {
638690

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

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

831+
/** Return a pair consisting of (supercall, rest)
832+
*
833+
* - supercall: the of superclass call, excluding trait constr calls
834+
*
835+
* The supercall is always the first statement (if exists)
836+
*/
837+
final def splitAtSuper(constrStats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) =
838+
constrStats.toList match {
839+
case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest)
840+
case stats => (Nil, stats)
841+
}
842+
831843
/** Structural tree comparison (since == on trees is reference equality).
832844
* For the moment, only Ident, Select, Literal, Apply and TypeApply are supported
833845
*/

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)