@@ -70,7 +70,6 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
7070 var claszSymbol : Symbol = null
7171 var isCZParcelable = false
7272 var isCZStaticModule = false
73- var initModuleInClinit = false
7473
7574 /* ---------------- idiomatic way to ask questions to typer ---------------- */
7675
@@ -102,26 +101,51 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
102101
103102 /* ---------------- helper utils for generating classes and fields ---------------- */
104103
105- def genPlainClass (cd : ClassDef ): Unit = {
104+ def genPlainClass (cd0 : ClassDef ): Unit = {
106105 assert(cnode == null , " GenBCode detected nested methods." )
107106
108- claszSymbol = cd .symbol
107+ claszSymbol = cd0 .symbol
109108 isCZParcelable = isAndroidParcelableClass(claszSymbol)
110109 isCZStaticModule = isStaticModuleClass(claszSymbol)
111110 thisBType = classBTypeFromSymbol(claszSymbol)
112- initModuleInClinit = isCZStaticModule && canAssignModuleInClinit(cd, claszSymbol)
113111
114112 cnode = new ClassNode1 ()
115113
116114 initJClass(cnode)
115+ val cd = if (isCZStaticModule) {
116+ // Move statements from the primary constructor following the superclass constructor call to
117+ // a newly synthesised tree representing the "<clinit>", which also assigns the MODULE$ field.
118+ // Because the assigments to both the module instance fields, and the fields of the module itself
119+ // are in the <clinit>, these fields can be static + final.
117120
118- val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor)
119- if ( ! hasStaticCtor) {
120- // but needs one ...
121- if (isCZStaticModule || isCZParcelable ) {
122- fabricateStaticInit( )
121+ // TODO should we do this transformation earlier, say in Constructors? Or would that just cause
122+ // pain for scala-{js, native}?
123+
124+ for (f <- fieldSymbols(claszSymbol) ) {
125+ f.setFlag( Flags . STATIC )
123126 }
124- }
127+ val constructorDefDef = treeInfo.firstConstructor(cd0.impl.body).asInstanceOf [DefDef ]
128+ val (uptoSuperStats, remainingConstrStats) = treeInfo.splitAtSuper(constructorDefDef.rhs.asInstanceOf [Block ].stats, classOnly = true )
129+ val clInitSymbol = claszSymbol.newMethod(nme.CLASS_CONSTRUCTOR , claszSymbol.pos, Flags .STATIC ).setInfo(NullaryMethodType (definitions.UnitTpe ))
130+
131+ // We don't need to enter this field into the decls of claszSymbol.info as this is added manually to the generated class
132+ // in addModuleInstanceField. TODO: try adding it to the decls and making the usual field generation do the right thing.
133+ val moduleField = claszSymbol.newValue(nme.MODULE_INSTANCE_FIELD , claszSymbol.pos, Flags .STATIC | Flags .PRIVATE ).setInfo(claszSymbol.tpeHK)
134+
135+ val callConstructor = NewFromConstructor (claszSymbol.primaryConstructor).setType(claszSymbol.tpeHK)
136+ val assignModuleField = Assign (global.gen.mkAttributedRef(moduleField).setType(claszSymbol.tpeHK), callConstructor).setType(definitions.UnitTpe )
137+ val remainingConstrStatsSubst = remainingConstrStats.map(_.substituteThis(claszSymbol, global.gen.mkAttributedRef(claszSymbol.sourceModule)).changeOwner(claszSymbol.primaryConstructor -> clInitSymbol))
138+ val clinit = DefDef (clInitSymbol, Block (assignModuleField :: remainingConstrStatsSubst, Literal (Constant (())).setType(definitions.UnitTpe )).setType(definitions.UnitTpe ))
139+ deriveClassDef(cd0)(tmpl => deriveTemplate(tmpl)(body =>
140+ clinit :: body.map {
141+ case `constructorDefDef` => copyDefDef(constructorDefDef)(rhs = Block (uptoSuperStats, constructorDefDef.rhs.asInstanceOf [Block ].expr))
142+ case tree => tree
143+ }
144+ ))
145+ } else cd0
146+
147+ val hasStaticCtor = methodSymbols(cd) exists (_.isStaticConstructor)
148+ if (! hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid()
125149
126150 val optSerial : Option [Long ] = serialVUID(claszSymbol)
127151 /* serialVersionUID can't be put on interfaces (it's a private field).
@@ -204,17 +228,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
204228 */
205229 private def addModuleInstanceField (): Unit = {
206230 // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
207- // scala/scala-dev#194:
208- // This can't be FINAL on JVM 1.9+ because we assign it from within the
209- // instance constructor, not from <clinit> directly. Assignment from <clinit>,
210- // after the constructor has completely finished, seems like the principled
211- // thing to do, but it would change behaviour when "benign" cyclic references
212- // between modules exist.
213- //
214- // We special case modules with parents that we know don't (and won't ever) refer to
215- // the module during their construction. These can use a final field, and defer the assigment
216- // to <clinit>.
217- val mods = if (initModuleInClinit) GenBCode .PublicStaticFinal else GenBCode .PublicStatic
231+ val mods = GenBCode .PublicStaticFinal
218232 val fv =
219233 cnode.visitField(mods,
220234 strMODULE_INSTANCE_FIELD,
@@ -232,7 +246,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
232246 /*
233247 * must-single-thread
234248 */
235- private def fabricateStaticInit (): Unit = {
249+ private def fabricateStaticInitAndroid (): Unit = {
236250
237251 val clinit : asm.MethodVisitor = cnode.visitMethod(
238252 GenBCode .PublicStatic , // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
@@ -243,20 +257,9 @@ abstract class BCodeSkelBuilder extends BCodeHelpers {
243257 )
244258 clinit.visitCode()
245259
246- /* "legacy static initialization" */
247- if (isCZStaticModule) {
248- clinit.visitTypeInsn(asm.Opcodes .NEW , thisBType.internalName)
249- if (initModuleInClinit) clinit.visitInsn(asm.Opcodes .DUP )
250-
251- clinit.visitMethodInsn(asm.Opcodes .INVOKESPECIAL ,
252- thisBType.internalName, INSTANCE_CONSTRUCTOR_NAME , " ()V" , false )
253- if (initModuleInClinit) {
254- assignModuleInstanceField(clinit)
255- }
256- }
257260 if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisBType.internalName) }
258- clinit.visitInsn(asm.Opcodes .RETURN )
259261
262+ clinit.visitInsn(asm.Opcodes .RETURN )
260263 clinit.visitMaxs(0 , 0 ) // just to follow protocol, dummy arguments
261264 clinit.visitEnd()
262265 }
0 commit comments