From 1974313f70a021a8686510bcbcb92c3cc2b7c605 Mon Sep 17 00:00:00 2001 From: Miguel Garcia Date: Mon, 8 Jul 2013 20:57:20 +0200 Subject: separation of concerns: delayed-init in constructors This commit modularizes TemplateTransformer by moving a few methods from TemplateTransformer into the newly added DelayedInitHelper. The methods in question - delayedInitCall() - delayedInitClosure() - delayedEndpointDef() build trees that rewriteDelayedInit() puts together. That way, all trace of rewriting related to delayed-init have vanished from TemplateTransformer and are encapsulated in DelayedInitHelper. --- .../scala/tools/nsc/transform/Constructors.scala | 271 +++++++++++---------- 1 file changed, 137 insertions(+), 134 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 7757ae49bf..320d63c917 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -90,7 +90,142 @@ abstract class Constructors extends Transform with ast.TreeDSL { } // ConstructorTransformer - class TemplateTransformer(unit: CompilationUnit, impl: Template) extends Transformer { + /* + * TemplateTransformer rewrites DelayedInit subclasses. + * The list of statements that will end up in the primary constructor can be split into: + * + * (a) up to and including the super-constructor call. + * These statements can occur only in the (bytecode-level) primary constructor. + * + * (b) remaining statements + * + * The purpose of DelayedInit is leaving (b) out of the primary constructor and have their execution "delayed". + * + * The rewriting to achieve "delayed initialization" involves: + * (c) an additional, synthetic, public method encapsulating (b) + * (d) an additional, synthetic closure whose argless apply() just invokes (c) + * (e) after executing the statements in (a), + * the primary constructor instantiates (d) and passes it as argument + * to a `delayedInit()` invocation on the current instance. + * In turn, `delayedInit()` is a method defined as abstract in the `DelayedInit` trait + * so that it can be overridden (for an example see `scala.App`) + * + * The following helper methods prepare Trees as part of this rewriting: + * + * (f) `delayedEndpointDef()` prepares (c). + * A transformer, `constrStatTransformer`, is used to re-locate statements (b) from template-level + * to become statements in method (c). The main task here is re-formulating accesses to params + * of the primary constructors (to recap, (c) has zero-params) in terms of param-accessor fields. + * In a Delayed-Init subclass, each class-constructor gets a param-accessor field because `mustbeKept()` forces it. + * + * (g) `delayedInitClosure()` prepares (d) + * + * (h) `delayedInitCall()` prepares the `delayedInit()` invocation referred to in (e) + * + * Both (c) and (d) are added to the Template returned by `transformClassTemplate()` + * + * A note of historic interest: Previously the rewriting for DelayedInit would include in the closure body + * all of the delayed initialization sequence, which in turn required: + * - reformulating "accesses-on-this" into "accesses-on-outer", and + * - adding public getters and setters. + * + * @param stats the statements in (b) above + * + * @return the DefDef for (c) above + * + * */ + trait DelayedInitHelper { self: TemplateTransformer => + + private def delayedEndpointDef(stats: List[Tree]): DefDef = { + + val methodName = currentUnit.freshTermName("delayedEndpoint$" + clazz.fullNameAsName('$').toString + "$") + val methodSym = clazz.newMethod(methodName, impl.pos, SYNTHETIC | FINAL) + methodSym setInfoAndEnter MethodType(Nil, UnitTpe) + + // changeOwner needed because the `stats` contained in the DefDef were owned by the template, not long ago. + val blk = Block(stats, gen.mkZero(UnitTpe)).changeOwner(impl.symbol -> methodSym) + val delayedDD = localTyper typed { DefDef(methodSym, Nil, blk) } + + delayedDD.asInstanceOf[DefDef] + } + + private def delayedInitClosure(delayedEndPointSym: MethodSymbol): ClassDef = { + val satelliteClass = localTyper.typed { + atPos(impl.pos) { + val closureClass = clazz.newClass(nme.delayedInitArg.toTypeName, impl.pos, SYNTHETIC | FINAL) + val closureParents = List(AbstractFunctionClass(0).tpe) + + closureClass setInfoAndEnter new ClassInfoType(closureParents, newScope, closureClass) + + val outerField: TermSymbol = ( + closureClass + newValue(nme.OUTER, impl.pos, PrivateLocal | PARAMACCESSOR) + setInfoAndEnter clazz.tpe + ) + val applyMethod: MethodSymbol = ( + closureClass + newMethod(nme.apply, impl.pos, FINAL) + setInfoAndEnter MethodType(Nil, ObjectTpe) + ) + val outerFieldDef = ValDef(outerField) + val closureClassTyper = localTyper.atOwner(closureClass) + val applyMethodTyper = closureClassTyper.atOwner(applyMethod) + + def applyMethodStat = + applyMethodTyper.typed { + atPos(impl.pos) { + val receiver = Select(This(closureClass), outerField) + Apply(Select(receiver, delayedEndPointSym), Nil) + } + } + + val applyMethodDef = DefDef( + sym = applyMethod, + vparamss = ListOfNil, + rhs = Block(applyMethodStat, gen.mkAttributedRef(BoxedUnit_UNIT))) + + ClassDef( + sym = closureClass, + constrMods = Modifiers(0), + vparamss = List(List(outerFieldDef)), + body = applyMethodDef :: Nil, + superPos = impl.pos) + } + } + + satelliteClass.asInstanceOf[ClassDef] + } + + private def delayedInitCall(closure: Tree) = localTyper.typedPos(impl.pos) { + gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(closure.symbol.tpe, This(clazz)))) + } + + def rewriteDelayedInit() { + /* XXX This is not corect: remainingConstrStats.nonEmpty excludes too much, + * but excluding it includes too much. The constructor sequence being mimicked + * needs to be reproduced with total fidelity. + * + * See test case files/run/bug4680.scala, the output of which is wrong in many + * particulars. + */ + val needsDelayedInit = (isDelayedInitSubclass && remainingConstrStats.nonEmpty) + + if (needsDelayedInit) { + val delayedHook: DefDef = delayedEndpointDef(remainingConstrStats) + defBuf += delayedHook + val hookCallerClass = { + // transform to make the closure-class' default constructor assign the the outer instance to its param-accessor field. + val drillDown = new ConstructorTransformer(unit) + drillDown transform delayedInitClosure(delayedHook.symbol.asInstanceOf[MethodSymbol]) + } + defBuf += hookCallerClass + remainingConstrStats = delayedInitCall(hookCallerClass) :: Nil + } + } + + } // DelayedInitHelper + + class TemplateTransformer(val unit: CompilationUnit, val impl: Template) extends Transformer with DelayedInitHelper { val clazz = impl.symbol.owner // the transformed class val stats = impl.body // the transformed template body @@ -506,119 +641,6 @@ abstract class Constructors extends Transform with ast.TreeDSL { } else stats } - /* - * Translation scheme for DelayedInit - * ---------------------------------- - * - * Before returning, transformClassTemplate() rewrites DelayedInit subclasses. - * The list of statements that will end up in the primary constructor can be split into: - * - * (a) up to and including the super-constructor call. - * These statements can occur only in the (bytecode-level) primary constructor. - * - * (b) remaining statements - * - * The purpose of DelayedInit is leaving (b) out of the primary constructor and have their execution "delayed". - * - * The rewriting to achieve "delayed initialization" involves: - * (c) an additional, synthetic, public method encapsulating (b) - * (d) an additional, synthetic closure whose argless apply() just invokes (c) - * (e) after executing the statements in (a), - * the primary constructor instantiates (d) and passes it as argument - * to a `delayedInit()` invocation on the current instance. - * In turn, `delayedInit()` is a method defined as abstract in the `DelayedInit` trait - * so that it can be overridden (for an example see `scala.App`) - * - * The following helper methods prepare Trees as part of this rewriting: - * - * (f) `delayedEndpointDef()` prepares (c). - * A transformer, `constrStatTransformer`, is used to re-locate statements (b) from template-level - * to become statements in method (c). The main task here is re-formulating accesses to params - * of the primary constructors (to recap, (c) has zero-params) in terms of param-accessor fields. - * In a Delayed-Init subclass, each class-constructor gets a param-accessor field because `mustbeKept()` forces it. - * - * (g) `delayedInitClosure()` prepares (d) - * - * (h) `delayedInitCall()` prepares the `delayedInit()` invocation referred to in (e) - * - * Both (c) and (d) are added to the Template returned by `transformClassTemplate()` - * - * A note of historic interest: Previously the rewriting for DelayedInit would include in the closure body - * all of the delayed initialization sequence, which in turn required: - * - reformulating "accesses-on-this" into "accesses-on-outer", and - * - adding public getters and setters. - * - * @param stats the statements in (b) above - * - * @return the DefDef for (c) above - * - * */ - def delayedEndpointDef(stats: List[Tree]): DefDef = { - - val methodName = currentUnit.freshTermName("delayedEndpoint$" + clazz.fullNameAsName('$').toString + "$") - val methodSym = clazz.newMethod(methodName, impl.pos, SYNTHETIC | FINAL) - methodSym setInfoAndEnter MethodType(Nil, UnitTpe) - - // changeOwner needed because the `stats` contained in the DefDef were owned by the template, not long ago. - val blk = Block(stats, gen.mkZero(UnitTpe)).changeOwner(impl.symbol -> methodSym) - val delayedDD = localTyper typed { DefDef(methodSym, Nil, blk) } - - delayedDD.asInstanceOf[DefDef] - } - - /* @see overview at `delayedEndpointDef()` of the translation scheme for DelayedInit */ - def delayedInitClosure(delayedEndPointSym: MethodSymbol): ClassDef = { - val satelliteClass = localTyper.typed { - atPos(impl.pos) { - val closureClass = clazz.newClass(nme.delayedInitArg.toTypeName, impl.pos, SYNTHETIC | FINAL) - val closureParents = List(AbstractFunctionClass(0).tpe) - - closureClass setInfoAndEnter new ClassInfoType(closureParents, newScope, closureClass) - - val outerField: TermSymbol = ( - closureClass - newValue(nme.OUTER, impl.pos, PrivateLocal | PARAMACCESSOR) - setInfoAndEnter clazz.tpe - ) - val applyMethod: MethodSymbol = ( - closureClass - newMethod(nme.apply, impl.pos, FINAL) - setInfoAndEnter MethodType(Nil, ObjectTpe) - ) - val outerFieldDef = ValDef(outerField) - val closureClassTyper = localTyper.atOwner(closureClass) - val applyMethodTyper = closureClassTyper.atOwner(applyMethod) - - def applyMethodStat = - applyMethodTyper.typed { - atPos(impl.pos) { - val receiver = Select(This(closureClass), outerField) - Apply(Select(receiver, delayedEndPointSym), Nil) - } - } - - val applyMethodDef = DefDef( - sym = applyMethod, - vparamss = ListOfNil, - rhs = Block(applyMethodStat, gen.mkAttributedRef(BoxedUnit_UNIT))) - - ClassDef( - sym = closureClass, - constrMods = Modifiers(0), - vparamss = List(List(outerFieldDef)), - body = applyMethodDef :: Nil, - superPos = impl.pos) - } - } - - satelliteClass.asInstanceOf[ClassDef] - } - - /* @see overview at `delayedEndpointDef()` of the translation scheme for DelayedInit */ - def delayedInitCall(closure: Tree) = localTyper.typedPos(impl.pos) { - gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(closure.symbol.tpe, This(clazz)))) - } - /* Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest) */ def splitAtSuper(stats: List[Tree]) = { def isConstr(tree: Tree): Boolean = tree match { @@ -633,26 +655,7 @@ abstract class Constructors extends Transform with ast.TreeDSL { val (uptoSuperStats, remainingConstrStats0) = splitAtSuper(constrStatBuf.toList) var remainingConstrStats = remainingConstrStats0 - /* XXX This is not corect: remainingConstrStats.nonEmpty excludes too much, - * but excluding it includes too much. The constructor sequence being mimicked - * needs to be reproduced with total fidelity. - * - * See test case files/run/bug4680.scala, the output of which is wrong in many - * particulars. - */ - val needsDelayedInit = (isDelayedInitSubclass && remainingConstrStats.nonEmpty) - - if (needsDelayedInit) { - val delayedHook: DefDef = delayedEndpointDef(remainingConstrStats) - defBuf += delayedHook - val hookCallerClass = { - // transform to make the closure-class' default constructor assign the the outer instance to its param-accessor field. - val drillDown = new ConstructorTransformer(unit) - drillDown transform delayedInitClosure(delayedHook.symbol.asInstanceOf[MethodSymbol]) - } - defBuf += hookCallerClass - remainingConstrStats = delayedInitCall(hookCallerClass) :: Nil - } + rewriteDelayedInit() // Assemble final constructor defBuf += deriveDefDef(constr)(_ => -- cgit v1.2.3