diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2015-11-19 11:02:56 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2015-11-19 11:02:56 -0800 |
commit | d3667465f42590e4d39cef557f48d0cd050dec42 (patch) | |
tree | 1489ae89ee19635204a1cdb3a47837dc15ce814c /src/compiler | |
parent | fe336167e7a9f68c7ea6beb80e2cbb69cea95ff8 (diff) | |
parent | 5160387dbd34e8f7d800244d0d6234a6f4540e73 (diff) | |
download | scala-d3667465f42590e4d39cef557f48d0cd050dec42.tar.gz scala-d3667465f42590e4d39cef557f48d0cd050dec42.tar.bz2 scala-d3667465f42590e4d39cef557f48d0cd050dec42.zip |
Merge pull request #4842 from adriaanm/fields-prep
Set the scene for phase 1 of the new trait encoding.
Diffstat (limited to 'src/compiler')
12 files changed, 420 insertions, 409 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 70582c82c8..26e2b75c2a 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -384,15 +384,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) def apply(unit: CompilationUnit): Unit - private val isErased = prev.name == "erasure" || prev.erasedTypes - override def erasedTypes: Boolean = isErased - private val isFlat = prev.name == "flatten" || prev.flatClasses - override def flatClasses: Boolean = isFlat - private val isSpecialized = prev.name == "specialize" || prev.specialized - override def specialized: Boolean = isSpecialized - private val isRefChecked = prev.name == "refchecks" || prev.refChecked - override def refChecked: Boolean = isRefChecked - /** Is current phase cancelled on this unit? */ def cancelled(unit: CompilationUnit) = { // run the typer only if in `createJavadoc` mode diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 18a495e5fd..1cf547a5ac 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -8,8 +8,9 @@ package scala.tools.nsc.backend.jvm import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} import java.io.{StringWriter, PrintWriter} import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} -import scala.tools.asm.{ClassWriter, Attribute, ClassReader} +import scala.tools.asm.{ClassReader, ClassWriter, Attribute} import scala.collection.convert.decorateAsScala._ +import scala.collection.convert.decorateAsJava._ import scala.tools.nsc.backend.jvm.analysis.InitialProducer import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype @@ -55,11 +56,30 @@ object AsmUtils { node } - def readClass(filename: String): ClassNode = { - val f = new java.io.RandomAccessFile(filename, "r") - val b = new Array[Byte](f.length.toInt) - f.read(b) - readClass(b) + def readClass(filename: String): ClassNode = readClass(classBytes(filename)) + + def classBytes(file: String): Array[Byte] = { + val f = new java.io.RandomAccessFile(file, "r") + val bytes = new Array[Byte](f.length.toInt) + f.read(bytes) + bytes + } + + def textifyClassStably(bytes: Array[Byte]): Unit = { + val node = new ClassNode() + new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) + + node.fields = node.fields.asScala.sortBy(_.name).asJava + node.methods = node.methods.asScala.sortBy(_.name).asJava + node.visibleAnnotations = null + node.attrs = null + node.invisibleAnnotations = null + + println(textify(node)) + } + + def main(args: Array[String]): Unit = { + textifyClassStably(classBytes(args.head)) } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index e1cee8861a..728a0b574d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -1052,7 +1052,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for (m <- moduleClass.info.membersBasedOnFlags(BCodeHelpers.ExcludedForwarderFlags, symtab.Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq definitions.ObjectClass) || m.isConstructor) - debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass'") + debuglog(s"No forwarder for '$m' from $jclassName to '$moduleClass': ${m.isType} || ${m.isDeferred} || ${m.owner eq definitions.ObjectClass} || ${m.isConstructor}") else if (conflictingNames(m.name)) log(s"No forwarder for $m due to conflict with ${linkedClass.info.member(m.name)}") else if (m.hasAccessBoundary) diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala index 82e7c76409..085a814c6b 100644 --- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala +++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala @@ -242,20 +242,18 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => } } - private def createMemberDef(tree: Tree, isForInterface: Boolean)(create: Tree => Tree) = { - val isInterfaceTree = tree.isDef && isInterfaceMember(tree.symbol) - if (isInterfaceTree && needsImplMethod(tree.symbol)) - create(tree) - else if (isInterfaceTree == isForInterface) - tree - else - EmptyTree - } - private def implMemberDef(tree: Tree): Tree = createMemberDef(tree, false)(implMethodDef) - private def ifaceMemberDef(tree: Tree): Tree = createMemberDef(tree, true)(t => DefDef(t.symbol, EmptyTree)) + private def isInterfaceTree(tree: Tree) = tree.isDef && isInterfaceMember(tree.symbol) + + private def deriveMemberForImplClass(tree: Tree): Tree = + if (isInterfaceTree(tree)) if (needsImplMethod(tree.symbol)) implMethodDef(tree) else EmptyTree + else tree + + private def deriveMemberForInterface(tree: Tree): Tree = + if (isInterfaceTree(tree)) if (needsImplMethod(tree.symbol)) DefDef(tree.symbol, EmptyTree) else tree + else EmptyTree private def ifaceTemplate(templ: Template): Template = - treeCopy.Template(templ, templ.parents, noSelfType, templ.body map ifaceMemberDef) + treeCopy.Template(templ, templ.parents, noSelfType, templ.body map deriveMemberForInterface) /** Transforms the member tree containing the implementation * into a member of the impl class. @@ -286,7 +284,7 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure => private def implTemplate(clazz: Symbol, templ: Template): Template = atPos(templ.pos) { val templ1 = ( - Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map implMemberDef)) + Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map deriveMemberForImplClass)) setSymbol clazz.newLocalDummy(templ.pos) ) templ1.changeOwner(templ.symbol.owner -> clazz, templ.symbol -> templ1.symbol) diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index c29826551b..1d98b5da31 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -49,7 +49,9 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { clearStatics() val newBody = transformTrees(body) val templ = deriveTemplate(tree)(_ => transformTrees(newStaticMembers.toList) ::: newBody) - try addStaticInits(templ, newStaticInits, localTyper) // postprocess to include static ctors + try + if (newStaticInits.isEmpty) templ + else deriveTemplate(templ)(body => staticConstructor(body, localTyper, templ.pos)(newStaticInits.toList) :: body) finally clearStatics() } private def mkTerm(prefix: String): TermName = unit.freshTermName(prefix) diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 48eb878e2a..f5eca11c98 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -6,8 +6,7 @@ package scala.tools.nsc package transform -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.ListBuffer +import scala.collection.mutable import scala.reflect.internal.util.ListOfNil import symtab.Flags._ @@ -28,7 +27,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { private val ctorParams: mutable.Map[Symbol, List[Symbol]] = perRunCaches.newMap[Symbol, List[Symbol]]() class ConstructorTransformer(unit: CompilationUnit) extends Transformer { - /* * Inspect for obvious out-of-order initialization; concrete, eager vals or vars, declared in this class, * for which a reference to the member precedes its definition. @@ -121,15 +119,15 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * What trees can be visited at this point? * To recap, by the time the constructors phase runs, local definitions have been hoisted out of their original owner. * Moreover, by the time elision is about to happen, the `intoConstructors` rewriting - * of template-level statements has taken place (the resulting trees can be found in `constrStatBuf`). + * of template-level statements has taken place (the resulting trees can be found in `constructorStats`). * * That means: * - * - nested classes are to be found in `defBuf` + * - nested classes are to be found in `defs` * - * - value and method definitions are also in `defBuf` and none of them contains local methods or classes. + * - value and method definitions are also in `defs` and none of them contains local methods or classes. * - * - auxiliary constructors are to be found in `auxConstructorBuf` + * - auxiliary constructors are to be found in `auxConstructors` * * Coming back to the question which trees may contain accesses: * @@ -148,70 +146,56 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * (the primary constructor) into a dedicated synthetic method that an anon-closure may invoke, as required by DelayedInit. * */ - private trait OmittablesHelper { self: TemplateTransformer => - - /* - * Initially populated with all elision candidates. - * Trees are traversed, and those candidates are removed which are actually needed. - * After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls. - */ - val omittables = mutable.Set.empty[Symbol] - - def populateOmittables() { - - omittables.clear() - - if(isDelayedInitSubclass) { - return - } + private trait OmittablesHelper { + def computeOmittableAccessors(clazz: Symbol, defs: List[Tree], auxConstructors: List[Tree]): Set[Symbol] = { + val decls = clazz.info.decls.toSet + val isEffectivelyFinal = clazz.isEffectivelyFinal + // Initially populated with all elision candidates. + // Trees are traversed, and those candidates are removed which are actually needed. + // After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls. + // // Note: elision of outer reference is based on a class-wise analysis, if a class might have subclasses, // it doesn't work. For example, `LocalParent` retains the outer reference in: // // class Outer { def test = {class LocalParent; class LocalChild extends LocalParent } } // // See run/t9408.scala for related test cases. - val isEffectivelyFinal = clazz.isEffectivelyFinal - def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal) - def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && isEffectivelyFinal && !sym.isOverridingSymbol) - - val decls = clazz.info.decls.toSet - val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (decls filter isParamCandidateForElision) - val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (decls filter isOuterCandidateForElision) - - omittables ++= paramCandidatesForElision - omittables ++= outerCandidatesForElision - - val bodyOfOuterAccessor: Map[Symbol, DefDef] = - defBuf.collect { case dd: DefDef if outerCandidatesForElision(dd.symbol) => dd.symbol -> dd }.toMap + def omittableParamAcc(sym: Symbol) = sym.isParamAccessor && sym.isPrivateLocal + def omittableOuterAcc(sym: Symbol) = isEffectivelyFinal && sym.isOuterAccessor && !sym.isOverridingSymbol + val omittables = mutable.Set.empty[Symbol] ++ (decls filter (sym => omittableParamAcc(sym) || omittableOuterAcc(sym))) // the closure only captures isEffectivelyFinal // no point traversing further once omittables is empty, all candidates ruled out already. object detectUsages extends Traverser { - private def markUsage(sym: Symbol) { - omittables -= debuglogResult("omittables -= ")(sym) - // recursive call to mark as needed the field supporting the outer-accessor-method. - bodyOfOuterAccessor get sym foreach (this traverse _.rhs) - } - override def traverse(tree: Tree): Unit = if (omittables.nonEmpty) { - def sym = tree.symbol - tree match { - // don't mark as "needed" the field supporting this outer-accessor, ie not just yet. - case _: DefDef if outerCandidatesForElision(sym) => () - case _: Select if omittables(sym) => markUsage(sym) ; super.traverse(tree) - case _ => super.traverse(tree) + lazy val bodyOfOuterAccessor = defs collect { case dd: DefDef if omittableOuterAcc(dd.symbol) => dd.symbol -> dd.rhs } toMap + + override def traverse(tree: Tree): Unit = + if (omittables.nonEmpty) { + def sym = tree.symbol + tree match { + case _: DefDef if (sym.owner eq clazz) && omittableOuterAcc(sym) => // don't mark as "needed" the field supporting this outer-accessor (not just yet) + case _: Select if omittables(sym) => omittables -= sym // mark usage + bodyOfOuterAccessor get sym foreach traverse // recurse to mark as needed the field supporting the outer-accessor-method + super.traverse(tree) + case _ => super.traverse(tree) + } } - } - def walk(xs: Seq[Tree]) = xs.iterator foreach traverse } - if (omittables.nonEmpty) { - detectUsages walk defBuf - detectUsages walk auxConstructorBuf - } - } - def mustBeKept(sym: Symbol) = !omittables(sym) + if (omittables.nonEmpty) + (defs.iterator ++ auxConstructors.iterator) foreach detectUsages.traverse + + omittables.toSet + } } // OmittablesHelper + trait ConstructorTransformerBase { + def unit: CompilationUnit + def impl: Template + def clazz: Symbol + def localTyper: analyzer.Typer + } + /* * TemplateTransformer rewrites DelayedInit subclasses. * The list of statements that will end up in the primary constructor can be split into: @@ -256,10 +240,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * @return the DefDef for (c) above * * */ - private trait DelayedInitHelper { self: TemplateTransformer => - + private trait DelayedInitHelper extends ConstructorTransformerBase { 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) @@ -318,36 +300,30 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { 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)))) - } + /** For a DelayedInit subclass, wrap remainingConstrStats into a DelayedInit closure. */ + def delayedInitDefsAndConstrStats(defs: List[Tree], remainingConstrStats: List[Tree]): (List[Tree], List[Tree]) = { + val delayedHook = delayedEndpointDef(remainingConstrStats) + val delayedHookSym = delayedHook.symbol.asInstanceOf[MethodSymbol] - def rewriteDelayedInit() { - /* XXX This is not correct: 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 + // transform to make the closure-class' default constructor assign the the outer instance to its param-accessor field. + val hookCallerClass = (new ConstructorTransformer(unit)) transform delayedInitClosure(delayedHookSym) + val delayedInitCall = localTyper.typedPos(impl.pos) { + gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(hookCallerClass.symbol.tpe, This(clazz)))) } + + (List(delayedHook, hookCallerClass), List(delayedInitCall)) } } // DelayedInitHelper - private trait GuardianOfCtorStmts { self: TemplateTransformer => + private trait GuardianOfCtorStmts extends ConstructorTransformerBase { + def primaryConstrParams: List[Symbol] + def usesSpecializedField: Boolean + + lazy val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE) + // The constructor of a non-specialized class that has specialized subclasses + // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields. + lazy val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED) /* Return a single list of statements, merging the generic class constructor with the * specialized stats. The original statements are retyped in the current class, and @@ -355,7 +331,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * `specializedStats` are replaced by the specialized assignment. */ private def mergeConstructors(genericClazz: Symbol, originalStats: List[Tree], specializedStats: List[Tree]): List[Tree] = { - val specBuf = new ListBuffer[Tree] + val specBuf = new mutable.ListBuffer[Tree] specBuf ++= specializedStats def specializedAssignFor(sym: Symbol): Option[Tree] = @@ -383,7 +359,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } log("merging: " + originalStats.mkString("\n") + "\nwith\n" + specializedStats.mkString("\n")) - val res = for (s <- originalStats; stat = s.duplicate) yield { + for (s <- originalStats; stat = s.duplicate) yield { log("merge: looking at " + stat) val stat1 = stat match { case Assign(sel @ Select(This(_), field), _) => @@ -413,9 +389,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { } else stat1 } - if (specBuf.nonEmpty) - println("residual specialized constructor statements: " + specBuf) - res +// if (specBuf.nonEmpty) +// println("residual specialized constructor statements: " + specBuf) } /* Add an 'if' around the statements coming after the super constructor. This @@ -435,7 +410,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { // postfix = postfix.tail // } - if (guardSpecializedFieldInit && intoConstructor.usesSpecializedField && stats.nonEmpty) { + if (guardSpecializedFieldInit && usesSpecializedField && stats.nonEmpty) { // save them for duplication in the specialized subclass guardedCtorStats(clazz) = stats ctorParams(clazz) = primaryConstrParams @@ -472,24 +447,23 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { with OmittablesHelper with GuardianOfCtorStmts { - val clazz = impl.symbol.owner // the transformed class - val stats = impl.body // the transformed template body - val localTyper = typer.atOwner(impl, clazz) - - val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE) - // The constructor of a non-specialized class that has specialized subclasses - // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields. - val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED) + val clazz = impl.symbol.owner // the transformed class + val localTyper = typer.atOwner(impl, clazz) val isDelayedInitSubclass = clazz isSubClass DelayedInitClass + private val stats = impl.body // the transformed template body + // find and dissect primary constructor - val (primaryConstr, primaryConstrParams, primaryConstrBody) = stats collectFirst { + private val (primaryConstr, _primaryConstrParams, primaryConstrBody) = stats collectFirst { case dd@DefDef(_, _, _, vps :: Nil, _, rhs: Block) if dd.symbol.isPrimaryConstructor => (dd, vps map (_.symbol), rhs) - } getOrElse abort("no constructor in template: impl = " + impl) + } getOrElse { + abort("no constructor in template: impl = " + impl) + } - // The parameter accessor fields which are members of the class - val paramAccessors = clazz.constrParamAccessors + + def primaryConstrParams = _primaryConstrParams + def usesSpecializedField = intoConstructor.usesSpecializedField // The constructor parameter corresponding to an accessor def parameter(acc: Symbol): Symbol = parameterNamed(acc.unexpandedName.getterName) @@ -564,15 +538,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { super.transform(tree) } - // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol - def apply(oldowner: Symbol, tree: Tree) = + // Move tree into constructor, take care of changing owner from `oldOwner` to `newOwner` (the primary constructor symbol) + def apply(oldOwner: Symbol, newOwner: Symbol)(tree: Tree) = if (tree eq EmptyTree) tree - else transform(tree.changeOwner(oldowner -> primaryConstr.symbol)) + else transform(tree.changeOwner(oldOwner -> newOwner)) } // Create an assignment to class field `to` with rhs `from` def mkAssign(to: Symbol, from: Tree): Tree = - localTyper.typedPos(to.pos) { Assign(Select(This(clazz), to), from) } + localTyper.typedPos(to.pos) { + Assign(Select(This(clazz), to), from) + } // Create code to copy parameter to parameter accessor field. // If parameter is $outer, check that it is not null so that we NPE @@ -582,73 +558,58 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { val result = mkAssign(to, Ident(from)) if (from.name != nme.OUTER || - from.tpe.typeSymbol.isPrimitiveValueClass) result + from.tpe.typeSymbol.isPrimitiveValueClass) result else localTyper.typedPos(to.pos) { // `throw null` has the same effect as `throw new NullPointerException`, see JVM spec on instruction `athrow` - IF (from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result + IF(from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result } } - // The list of definitions that go into class - val defBuf = new ListBuffer[Tree] - - // The auxiliary constructors, separate from the defBuf since they should - // follow the primary constructor - val auxConstructorBuf = new ListBuffer[Tree] - - // The list of statements that go into the constructor after and including the superclass constructor call - val constrStatBuf = new ListBuffer[Tree] - - // The list of early initializer statements that go into constructor before the superclass constructor call - val constrPrefixBuf = new ListBuffer[Tree] + // Constant typed vals are not memoized. + def memoizeValue(sym: Symbol) = !sym.info.isInstanceOf[ConstantType] - // The early initialized field definitions of the class (these are the class members) - val presupers = treeInfo.preSuperFields(stats) + /** Triage definitions and statements in this template into the following categories. + * The primary constructor is treated separately, as it is assembled in part from these pieces. + * + * - `defs`: definitions that go into class + * - `auxConstrs`: auxiliary constructors, separate from the defs as they should follow the primary constructor + * - `constrPrefix`: early initializer statements that go into constructor before the superclass constructor call + * - `constrStats`: statements that go into the constructor after and including the superclass constructor call + * - `classInitStats`: statements that go into the class initializer + */ + def triageStats = { + val defBuf, auxConstructorBuf, constrPrefixBuf, constrStatBuf, classInitStatBuf = new mutable.ListBuffer[Tree] - // The list of statements that go into the class initializer - val classInitStatBuf = new ListBuffer[Tree] + // The early initialized field definitions of the class (these are the class members) + val presupers = treeInfo.preSuperFields(stats) - // generate code to copy pre-initialized fields - for (stat <- primaryConstrBody.stats) { - constrStatBuf += stat - stat match { - case ValDef(mods, name, _, _) if mods hasFlag PRESUPER => - // stat is the constructor-local definition of the field value - val fields = presupers filter (_.getterName == name) - assert(fields.length == 1) - val to = fields.head.symbol - if (!to.tpe.isInstanceOf[ConstantType]) - constrStatBuf += mkAssign(to, Ident(stat.symbol)) - case _ => + // generate code to copy pre-initialized fields + for (stat <- primaryConstrBody.stats) { + constrStatBuf += stat + stat match { + case ValDef(mods, name, _, _) if mods hasFlag PRESUPER => + // stat is the constructor-local definition of the field value + val fields = presupers filter (_.getterName == name) + assert(fields.length == 1, s"expected exactly one field by name $name in $presupers of $clazz's early initializers") + val to = fields.head.symbol + + if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol)) + case _ => + } } - } - - - for (stat <- stats) { - val statSym = stat.symbol - - stat match { - // recurse on class definition, store in defBuf - case _: ClassDef => defBuf += new ConstructorTransformer(unit).transform(stat) - - // methods (except primary constructor) go into template - // (non-primary ctors --> auxConstructorBuf / regular defs --> defBuf) - case _: DefDef if statSym.isPrimaryConstructor => () - case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat - case _: DefDef => defBuf += stat - // val defs with constant right-hand sides are eliminated. - case _: ValDef if statSym.info.isInstanceOf[ConstantType] => () - - // For all other val defs, an empty valdef goes into the template. - // Additionally, non-lazy vals are initialized by an assignment in: - // - the class initializer (static), - // - the constructor, before the super call (early initialized or a parameter accessor), - // - the constructor, after the super call (regular val). - case ValDef(mods, _, _, rhs) => + for (stat <- stats) { + val statSym = stat.symbol + + // Move the RHS of a ValDef to the appropriate part of the ctor. + // If the val is an early initialized or a parameter accessor, + // it goes before the superclass constructor call, otherwise it goes after. + // A lazy val's effect is not moved to the constructor, as it is delayed. + // Returns `true` when a `ValDef` is needed. + def moveEffectToCtor(mods: Modifiers, rhs: Tree, assignSym: Symbol): Unit = { val initializingRhs = - if (statSym.isLazy) EmptyTree - else if (!mods.hasStaticFlag) intoConstructor(statSym, rhs) + if ((assignSym eq NoSymbol) || statSym.isLazy) EmptyTree // not memoized, or effect delayed (for lazy val) + else if (!mods.hasStaticFlag) intoConstructor(statSym, primaryConstr.symbol)(rhs) else rhs if (initializingRhs ne EmptyTree) { @@ -657,68 +618,120 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf else constrStatBuf - initPhase += mkAssign(statSym, initializingRhs) + initPhase += mkAssign(assignSym, initializingRhs) } + } - defBuf += deriveValDef(stat)(_ => EmptyTree) - - // all other statements go into the constructor - case _ => constrStatBuf += intoConstructor(impl.symbol, stat) + stat match { + // recurse on class definition, store in defBuf + case _: ClassDef => defBuf += new ConstructorTransformer(unit).transform(stat) + + // Triage methods -- they all end up in the template -- + // regular ones go to `defBuf`, secondary contructors go to `auxConstructorBuf`. + // The primary constructor is dealt with separately (we're massaging it here). + case _: DefDef if statSym.isPrimaryConstructor => () + case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat + case _: DefDef => defBuf += stat + + // If a val needs a field, an empty valdef goes into the template. + // Except for lazy and ConstantTyped vals, the field is initialized by an assignment in: + // - the class initializer (static), + // - the constructor, before the super call (early initialized or a parameter accessor), + // - the constructor, after the super call (regular val). + case ValDef(mods, _, _, rhs) => + if (rhs ne EmptyTree) { + val emitField = memoizeValue(statSym) + moveEffectToCtor(mods, rhs, if (emitField) statSym else NoSymbol) + if (emitField) defBuf += deriveValDef(stat)(_ => EmptyTree) + } else defBuf += stat + + // all other statements go into the constructor + case _ => constrStatBuf += intoConstructor(impl.symbol, primaryConstr.symbol)(stat) + } } - } - - populateOmittables() - - // Initialize all parameters fields that must be kept. - val paramInits = paramAccessors filter mustBeKept map { acc => - // Check for conflicting symbol amongst parents: see bug #1960. - // It would be better to mangle the constructor parameter name since - // it can only be used internally, but I think we need more robust name - // mangling before we introduce more of it. - val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait) - if (conflict ne NoSymbol) - reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString)) - copyParam(acc, parameter(acc)) + (defBuf.toList, auxConstructorBuf.toList, constrPrefixBuf.toList, constrStatBuf.toList, classInitStatBuf.toList) } - /* 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 { - case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks - case _ => (tree.symbol ne null) && tree.symbol.isConstructor + def transformed = { + val (defs, auxConstructors, constructorPrefix, constructorStats, classInitStats) = triageStats + + // omit unused outers + val omittableAccessor: Set[Symbol] = + if (isDelayedInitSubclass) Set.empty + else computeOmittableAccessors(clazz, defs, auxConstructors) + + // TODO: this should omit fields for non-memoized (constant-typed, unit-typed vals need no storage -- + // all the action is in the getter) + def omittableSym(sym: Symbol) = omittableAccessor(sym) + def omittableStat(stat: Tree) = omittableSym(stat.symbol) + + // The parameter accessor fields which are members of the class + val paramAccessors = clazz.constrParamAccessors + + // Initialize all parameters fields that must be kept. + val paramInits = paramAccessors filterNot omittableSym map { acc => + // Check for conflicting symbol amongst parents: see bug #1960. + // It would be better to mangle the constructor parameter name since + // it can only be used internally, but I think we need more robust name + // mangling before we introduce more of it. + val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait) + if (conflict ne NoSymbol) + reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString)) + + copyParam(acc, parameter(acc)) } - val (pre, rest0) = stats span (!isConstr(_)) - val (supercalls, rest) = rest0 span (isConstr(_)) - (pre ::: supercalls, rest) - } - - val (uptoSuperStats, remainingConstrStats0) = splitAtSuper(constrStatBuf.toList) - var remainingConstrStats = remainingConstrStats0 - - rewriteDelayedInit() - - // Assemble final constructor - defBuf += deriveDefDef(primaryConstr)(_ => - treeCopy.Block( - primaryConstrBody, - paramInits ::: constrPrefixBuf.toList ::: uptoSuperStats ::: - guardSpecializedInitializer(remainingConstrStats), - primaryConstrBody.expr)) - // Followed by any auxiliary constructors - defBuf ++= auxConstructorBuf - - // Unlink all fields that can be dropped from class scope - for (sym <- clazz.info.decls ; if !mustBeKept(sym)) - clazz.info.decls unlink sym - - // Eliminate all field definitions that can be dropped from template - val templateWithoutOmittables: Template = deriveTemplate(impl)(_ => defBuf.toList filter (stat => mustBeKept(stat.symbol))) + // 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 { + case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks + case _ => (tree.symbol ne null) && tree.symbol.isConstructor + } + val (pre, rest0) = stats span (!isConstr(_)) + val (supercalls, rest) = rest0 span (isConstr(_)) + (pre ::: supercalls, rest) + } - // Add the static initializers - val transformed: Template = addStaticInits(templateWithoutOmittables, classInitStatBuf, localTyper) + val (uptoSuperStats, remainingConstrStats) = splitAtSuper(constructorStats) + /* TODO: XXX This condition (`isDelayedInitSubclass && remainingConstrStats.nonEmpty`) is not correct: + * 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 (delayedHookDefs, remainingConstrStatsDelayedInit) = + if (isDelayedInitSubclass && remainingConstrStats.nonEmpty) delayedInitDefsAndConstrStats(defs, remainingConstrStats) + else (Nil, remainingConstrStats) + + // Assemble final constructor + val primaryConstructor = deriveDefDef(primaryConstr)(_ => { + treeCopy.Block( + primaryConstrBody, + paramInits ::: constructorPrefix ::: uptoSuperStats ::: guardSpecializedInitializer(remainingConstrStatsDelayedInit), + primaryConstrBody.expr) + }) + + val constructors = primaryConstructor :: auxConstructors + + // Unlink all fields that can be dropped from class scope + // Iterating on toList is cheaper (decls.filter does a toList anyway) + val decls = clazz.info.decls + decls.toList.filter(omittableSym).foreach(decls.unlink) + + // Eliminate all field/accessor definitions that can be dropped from template + // We never eliminate delayed hooks or the constructors, so, only filter `defs`. + val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors + + // Add the static initializers + if (classInitStats.isEmpty) deriveTemplate(impl)(_ => prunedStats) + else { + val staticCtor = staticConstructor(prunedStats, localTyper, impl.pos)(classInitStats) + deriveTemplate(impl)(_ => staticCtor :: prunedStats) + } + } } // TemplateTransformer - } diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index f12f6c4e18..854acd63c0 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -159,19 +159,24 @@ abstract class ExplicitOuter extends InfoTransform * elides outer pointers. */ def transformInfo(sym: Symbol, tp: Type): Type = tp match { - case MethodType(params, restpe1) => - val restpe = transformInfo(sym, restpe1) - if (sym.owner.isTrait && ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule)) { // 5 - sym.makeNotPrivate(sym.owner) + case MethodType(params, resTp) => + val resTpTransformed = transformInfo(sym, resTp) + + // juggle flags (and mangle names) after transforming info + if (sym.owner.isTrait) { + // TODO: I don't believe any private accessors remain after the fields phase + if ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule) sym.makeNotPrivate(sym.owner) // 5 + if (sym.isProtected) sym setFlag notPROTECTED // 6 } - if (sym.owner.isTrait && sym.isProtected) sym setFlag notPROTECTED // 6 - if (sym.isClassConstructor && isInner(sym.owner)) { // 1 - val p = sym.newValueParameter(innerClassConstructorParamName, sym.pos) - .setInfo(sym.owner.outerClass.thisType) - MethodType(p :: params, restpe) - } else if (restpe ne restpe1) - MethodType(params, restpe) + + val paramsWithOuter = + if (sym.isClassConstructor && isInner(sym.owner)) // 1 + sym.newValueParameter(innerClassConstructorParamName, sym.pos).setInfo(sym.owner.outerClass.thisType) :: params + else params + + if ((resTpTransformed ne resTp) || (paramsWithOuter ne params)) MethodType(paramsWithOuter, resTpTransformed) else tp + case ClassInfoType(parents, decls, clazz) => var decls1 = decls if (isInner(clazz) && !clazz.isInterface) { diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index d1be1558b9..01714e22fd 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -148,7 +148,7 @@ abstract class LambdaLift extends InfoTransform { * } */ private def markFree(sym: Symbol, enclosure: Symbol): Boolean = { - debuglog("mark free: " + sym.fullLocationString + " marked free in " + enclosure) +// println(s"mark free: ${sym.fullLocationString} marked free in $enclosure") (enclosure == sym.owner.logicallyEnclosingMember) || { debuglog("%s != %s".format(enclosure, sym.owner.logicallyEnclosingMember)) if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false @@ -158,7 +158,7 @@ abstract class LambdaLift extends InfoTransform { ss += sym renamable += sym changedFreeVars = true - debuglog("" + sym + " is free in " + enclosure) + debuglog(s"$sym is free in $enclosure") if (sym.isVariable) sym setFlag CAPTURED } !enclosure.isClass @@ -167,7 +167,7 @@ abstract class LambdaLift extends InfoTransform { } private def markCalled(sym: Symbol, owner: Symbol) { - debuglog("mark called: " + sym + " of " + sym.owner + " is called by " + owner) +// println(s"mark called: $sym of ${sym.owner} is called by $owner") symSet(called, owner) += sym if (sym.enclClass != owner.enclClass) calledFromInner += sym } @@ -175,7 +175,7 @@ abstract class LambdaLift extends InfoTransform { /** The traverse function */ private val freeVarTraverser = new Traverser { override def traverse(tree: Tree) { - try { //debug +// try { //debug val sym = tree.symbol tree match { case ClassDef(_, _, _, _) => @@ -222,11 +222,11 @@ abstract class LambdaLift extends InfoTransform { case _ => } super.traverse(tree) - } catch {//debug - case ex: Throwable => - Console.println(s"$ex while traversing $tree") - throw ex - } +// } catch {//debug +// case ex: Throwable => +// Console.println(s"$ex while traversing $tree") +// throw ex +// } } } @@ -240,7 +240,7 @@ abstract class LambdaLift extends InfoTransform { do { changedFreeVars = false - for (caller <- called.keys ; callee <- called(caller) ; fvs <- free get callee ; fv <- fvs) + for ((caller, callees) <- called ; callee <- callees ; fvs <- free get callee ; fv <- fvs) markFree(fv, caller) } while (changedFreeVars) @@ -308,11 +308,14 @@ abstract class LambdaLift extends InfoTransform { afterOwnPhase { for ((owner, freeValues) <- free.toList) { - val newFlags = SYNTHETIC | ( if (owner.isClass) PARAMACCESSOR | PrivateLocal else PARAM ) - debuglog("free var proxy: %s, %s".format(owner.fullLocationString, freeValues.toList.mkString(", "))) + val newFlags = SYNTHETIC | ( + if (owner.isClass) PARAMACCESSOR | PrivateLocal + else PARAM) + proxies(owner) = for (fv <- freeValues.toList) yield { val proxyName = proxyNames.getOrElse(fv, fv.name) + debuglog(s"new proxy ${proxyName} in ${owner.fullLocationString}") val proxy = owner.newValue(proxyName.toTermName, owner.pos, newFlags.toLong) setInfo fv.info if (owner.isClass) owner.info.decls enter proxy proxy @@ -342,31 +345,31 @@ abstract class LambdaLift extends InfoTransform { private def memberRef(sym: Symbol): Tree = { val clazz = sym.owner.enclClass - //Console.println("memberRef from "+currentClass+" to "+sym+" in "+clazz) - def prematureSelfReference() { + // println(s"memberRef from $currentClass to $sym in $clazz (currentClass=$currentClass)") + def prematureSelfReference(): Tree = { val what = if (clazz.isStaticOwner) clazz.fullLocationString else s"the unconstructed `this` of ${clazz.fullLocationString}" val msg = s"Implementation restriction: access of ${sym.fullLocationString} from ${currentClass.fullLocationString}, would require illegal premature access to $what" reporter.error(curTree.pos, msg) + EmptyTree } - val qual = + def qual = if (clazz == currentClass) gen.mkAttributedThis(clazz) else { sym resetFlag (LOCAL | PRIVATE) - if (isUnderConstruction(clazz)) { - prematureSelfReference() - EmptyTree - } + if (isUnderConstruction(clazz)) prematureSelfReference() else if (clazz.isStaticOwner) gen.mkAttributedQualifier(clazz.thisType) - else { - outerValue match { - case EmptyTree => prematureSelfReference(); return EmptyTree - case o => outerPath(o, currentClass.outerClass, clazz) - } + else outerValue match { + case EmptyTree => prematureSelfReference() + case o => outerPath(o, currentClass.outerClass, clazz) } } - Select(qual, sym) setType sym.tpe + + qual match { + case EmptyTree => EmptyTree + case qual => Select(qual, sym) setType sym.tpe + } } private def proxyRef(sym: Symbol) = { @@ -374,41 +377,45 @@ abstract class LambdaLift extends InfoTransform { if (psym.isLocalToBlock) gen.mkAttributedIdent(psym) else memberRef(psym) } - private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = { - free get sym match { - case Some(fvs) => addFree(sym, free = fvs.toList map (fv => atPos(pos)(proxyRef(fv))), original = args) - case _ => args + def freeArgsOrNil(sym: Symbol) = free.getOrElse(sym, Nil).toList + + private def freeArgs(sym: Symbol): List[Symbol] = + freeArgsOrNil(sym) + + private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = + freeArgs(sym) match { + case Nil => args + case fvs => addFree(sym, free = fvs map (fv => atPos(pos)(proxyRef(fv))), original = args) } - } - private def addFreeParams(tree: Tree, sym: Symbol): Tree = proxies.get(sym) match { - case Some(ps) => - val freeParams = ps map (p => ValDef(p) setPos tree.pos setType NoType) - tree match { - case DefDef(_, _, _, vparams :: _, _, _) => - val addParams = cloneSymbols(ps).map(_.setFlag(PARAM)) - sym.updateInfo( - lifted(MethodType(addFree(sym, free = addParams, original = sym.info.params), sym.info.resultType))) + def proxiesOrNil(sym: Symbol) = proxies.getOrElse(sym, Nil) + + private def freeParams(sym: Symbol): List[Symbol] = + proxiesOrNil(sym) + + private def addFreeParams(tree: Tree, sym: Symbol): Tree = + tree match { + case DefDef(_, _, _, vparams :: _, _, _) => + val ps = freeParams(sym) + + if (ps isEmpty) tree + else { + val paramSyms = cloneSymbols(ps).map(_.setFlag(PARAM)) + val paramDefs = ps map (p => ValDef(p) setPos tree.pos setType NoType) + + sym.updateInfo(lifted(MethodType(addFree(sym, free = paramSyms, original = sym.info.params), sym.info.resultType))) + copyDefDef(tree)(vparamss = List(addFree(sym, free = paramDefs, original = vparams))) + } + + case ClassDef(_, _, _, _) => + val freeParamDefs = freeParams(sym) map (p => ValDef(p) setPos tree.pos setType NoType) + + if (freeParamDefs isEmpty) tree + else deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParamDefs)) + + case _ => tree + } - copyDefDef(tree)(vparamss = List(addFree(sym, free = freeParams, original = vparams))) - case ClassDef(_, _, _, _) => - // SI-6231 - // Disabled attempt to to add getters to freeParams - // this does not work yet. Problem is that local symbols need local names - // and references to local symbols need to be transformed into - // method calls to setters. - // def paramGetter(param: Symbol): Tree = { - // val getter = param.newGetter setFlag TRANS_FLAG resetFlag PARAMACCESSOR // mark because we have to add them to interface - // sym.info.decls.enter(getter) - // val rhs = Select(gen.mkAttributedThis(sym), param) setType param.tpe - // DefDef(getter, rhs) setPos tree.pos setType NoType - // } - // val newDefs = if (sym.isTrait) freeParams ::: (ps map paramGetter) else freeParams - deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParams)) - } - case None => - tree - } /* SI-6231: Something like this will be necessary to eliminate the implementation * restriction from paramGetter above: @@ -451,7 +458,7 @@ abstract class LambdaLift extends InfoTransform { // See neg/t1909-object.scala def msg = s"SI-1909 Unable to STATICally lift $sym, which is defined in the self- or super-constructor call of ${sym.owner.owner}. A VerifyError is likely." devWarning(tree.pos, msg) - } else sym setFlag STATIC + } else sym setFlag STATIC } sym.owner = sym.owner.enclClass @@ -468,12 +475,11 @@ abstract class LambdaLift extends InfoTransform { private def postTransform(tree: Tree, isBoxedRef: Boolean = false): Tree = { val sym = tree.symbol tree match { - case ClassDef(_, _, _, _) => - val tree1 = addFreeParams(tree, sym) - if (sym.isLocalToBlock) liftDef(tree1) else tree1 - case DefDef(_, _, _, _, _, _) => - val tree1 = addFreeParams(tree, sym) - if (sym.isLocalToBlock) liftDef(tree1) else tree1 + case _: ClassDef | _: DefDef => + val withFreeParams = addFreeParams(tree, sym) + if (sym.isLocalToBlock) liftDef(withFreeParams) + else withFreeParams + case ValDef(mods, name, tpt, rhs) => if (sym.isCapturedVariable) { val tpt1 = TypeTree(sym.tpe) setPos tpt.pos diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index b6695efb0b..93e41dde7b 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -38,6 +38,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) => + // Avoid adding bitmaps when they are fully overshadowed by those that are added inside loops case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) => case _ => @@ -108,22 +109,16 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD } case Template(_, _, body) => atOwner(currentOwner) { - val body1 = super.transformTrees(body) + // TODO: shady business... can this logic be encapsulated in LocalLazyValFinder? var added = false - val stats = - for (stat <- body1) yield stat match { - case Block(_, _) | Apply(_, _) | If(_, _, _) | Try(_, _, _) if !added => - // Avoid adding bitmaps when they are fully overshadowed by those - // that are added inside loops - if (LocalLazyValFinder.find(stat)) { - added = true - typed(addBitmapDefs(sym, stat)) - } else stat - case ValDef(_, _, _, _) => - typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) - case _ => - stat + val stats = super.transformTrees(body) mapConserve { + case stat: ValDef => typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) + case stat: TermTree if !added && (LocalLazyValFinder find stat) => + added = true + typed(addBitmapDefs(sym, stat)) + case stat => stat } + val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) { // add bitmap to inner class if necessary val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO))) diff --git a/src/compiler/scala/tools/nsc/transform/Statics.scala b/src/compiler/scala/tools/nsc/transform/Statics.scala index 4673be6de7..9ab00f1a83 100644 --- a/src/compiler/scala/tools/nsc/transform/Statics.scala +++ b/src/compiler/scala/tools/nsc/transform/Statics.scala @@ -1,49 +1,32 @@ package scala.tools.nsc package transform -import collection.mutable.Buffer - abstract class Statics extends Transform with ast.TreeDSL { import global._ class StaticsTransformer extends Transformer { - - /** finds the static ctor DefDef tree within the template if it exists. */ - def findStaticCtor(template: Template): Option[Tree] = - template.body find { - case defdef @ DefDef(_, nme.CONSTRUCTOR, _, _, _, _) => defdef.symbol.hasStaticFlag - case _ => false - } - - /** changes the template for the class so that it contains a static constructor with symbol fields inits, - * augments an existing static ctor if one already existed. + /** generate a static constructor with symbol fields inits, or an augmented existing static ctor */ - def addStaticInits(template: Template, newStaticInits: Buffer[Tree], localTyper: analyzer.Typer): Template = { - if (newStaticInits.isEmpty) - template - else { - val newCtor = findStaticCtor(template) match { - // in case there already were static ctors - augment existing ones - // currently, however, static ctors aren't being generated anywhere else - case Some(ctor @ DefDef(_,_,_,_,_,_)) => - // modify existing static ctor - deriveDefDef(ctor) { - case block @ Block(stats, expr) => - // need to add inits to existing block - treeCopy.Block(block, newStaticInits.toList ::: stats, expr) - case term: TermTree => - // need to create a new block with inits and the old term - treeCopy.Block(term, newStaticInits.toList, term) - } - case _ => - // create new static ctor - val staticCtorSym = currentClass.newStaticConstructor(template.pos) - val rhs = Block(newStaticInits.toList, Literal(Constant(()))) + def staticConstructor(body: List[Tree], localTyper: analyzer.Typer, pos: Position)(newStaticInits: List[Tree]): Tree = + body.collectFirst { + // If there already was a static ctor - augment existing one + // currently, however, static ctors aren't being generated anywhere else (!!!) + case ctor@DefDef(_, nme.CONSTRUCTOR, _, _, _, _) if ctor.symbol.hasStaticFlag => + // modify existing static ctor + deriveDefDef(ctor) { + case block@Block(stats, expr) => + // need to add inits to existing block + treeCopy.Block(block, newStaticInits ::: stats, expr) + case term: TermTree => + // need to create a new block with inits and the old term + treeCopy.Block(term, newStaticInits, term) + } + } getOrElse { + // create new static ctor + val staticCtorSym = currentClass.newStaticConstructor(pos) + val rhs = Block(newStaticInits, Literal(Constant(()))) - localTyper.typedPos(template.pos)(DefDef(staticCtorSym, rhs)) - } - deriveTemplate(template)(newCtor :: _) + localTyper.typedPos(pos)(DefDef(staticCtorSym, rhs)) } - } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index f3632b144d..05fe60f7b7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -22,17 +22,6 @@ trait MethodSynthesis { import definitions._ import CODE._ - /** The annotations amongst those found on the original symbol which - * should be propagated to this kind of accessor. - */ - def deriveAnnotations(initial: List[AnnotationInfo], category: Symbol, keepClean: Boolean): List[AnnotationInfo] = { - def annotationFilter(ann: AnnotationInfo) = ann.metaAnnotations match { - case Nil if ann.defaultTargets.isEmpty => keepClean // no meta-annotations or default targets - case Nil => ann.defaultTargets contains category // default targets exist for ann - case metas => metas exists (_ matches category) // meta-annotations attached to ann - } - initial filter annotationFilter - } class ClassMethodSynthesis(val clazz: Symbol, localTyper: Typer) { def mkThis = This(clazz) setPos clazz.pos.focus @@ -160,13 +149,15 @@ trait MethodSynthesis { enterBeans(tree) } + import AnnotationInfo.{mkFilter => annotationFilter} + /** This is called for those ValDefs which addDerivedTrees ignores, but * which might have a warnable annotation situation. */ private def warnForDroppedAnnotations(tree: Tree) { val annotations = tree.symbol.initialize.annotations val targetClass = defaultAnnotationTarget(tree) - val retained = deriveAnnotations(annotations, targetClass, keepClean = true) + val retained = annotations filter annotationFilter(targetClass, defaultRetention = true) annotations filterNot (retained contains _) foreach (ann => issueAnnotationWarning(tree, ann, targetClass)) } @@ -208,8 +199,8 @@ trait MethodSynthesis { context.unit.synthetics get meth match { case Some(mdef) => context.unit.synthetics -= meth - meth setAnnotations deriveAnnotations(annotations, MethodTargetClass, keepClean = false) - cd.symbol setAnnotations deriveAnnotations(annotations, ClassTargetClass, keepClean = true) + meth setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + cd.symbol setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) List(cd, mdef) case _ => // Shouldn't happen, but let's give ourselves a reasonable error when it does @@ -293,10 +284,6 @@ trait MethodSynthesis { def tree: ValDef final def enclClass = basisSym.enclClass - /** Which meta-annotation is associated with this kind of entity. - * Presently one of: field, getter, setter, beanGetter, beanSetter, param. - */ - def category: Symbol /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ final def completer(sym: Symbol) = namerOf(sym).accessorTypeCompleter(tree, isSetter) @@ -307,7 +294,6 @@ trait MethodSynthesis { def isSetter = false def isDeferred = mods.isDeferred - def keepClean = false // whether annotations whose definitions are not meta-annotated should be kept. def validate() { } def createAndEnterSymbol(): MethodSymbol = { val sym = owner.newMethod(name, tree.pos.focus, derivedMods.flags) @@ -323,7 +309,29 @@ trait MethodSynthesis { } final def derive(initial: List[AnnotationInfo]): Tree = { validate() - derivedSym setAnnotations deriveAnnotations(initial, category, keepClean) + + // see scala.annotation.meta's package class for more info + // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. + // The defaults are: + // - (`val`-, `var`- or plain) constructor parameter annotations end up on the parameter, not on any other entity. + // - val/var member annotations solely end up on the underlying field. + // + // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit? + // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter) + val annotFilter: AnnotationInfo => Boolean = this match { + case _: Param => annotationFilter(ParamTargetClass, defaultRetention = true) + // By default annotations go to the field, except if the field is generated for a class parameter (PARAMACCESSOR). + case _: Field => annotationFilter(FieldTargetClass, defaultRetention = !mods.isParamAccessor) + case _: BaseGetter => annotationFilter(GetterTargetClass, defaultRetention = false) + case _: Setter => annotationFilter(SetterTargetClass, defaultRetention = false) + case _: BeanSetter => annotationFilter(BeanSetterTargetClass, defaultRetention = false) + case _: AnyBeanGetter => annotationFilter(BeanGetterTargetClass, defaultRetention = false) + } + + // The annotations amongst those found on the original symbol which + // should be propagated to this kind of accessor. + derivedSym setAnnotations (initial filter annotFilter) + logDerived(derivedTree) } } @@ -370,7 +378,6 @@ trait MethodSynthesis { sealed abstract class BaseGetter(tree: ValDef) extends DerivedGetter { def name = tree.name - def category = GetterTargetClass def flagsMask = GetterFlags def flagsExtra = ACCESSOR.toLong | ( if (tree.mods.isMutable) 0 else STABLE ) @@ -395,12 +402,13 @@ trait MethodSynthesis { // starts compiling (instead of failing like it's supposed to) because the typer // expects to be able to identify escaping locals in typedDefDef, and fails to // spot that brand of them. In other words it's an artifact of the implementation. - val tpt = derivedSym.tpe_*.finalResultType.widen match { + val getterTp = derivedSym.tpe_*.finalResultType + val tpt = getterTp.widen match { // Range position errors ensue if we don't duplicate this in some // circumstances (at least: concrete vals with existential types.) - case ExistentialType(_, _) => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) - case _ if isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field - case tp => TypeTree(tp) + case _: ExistentialType => TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) + case _ if isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field + case _ => TypeTree(getterTp) } tpt setPos tree.tpt.pos.focus } @@ -449,7 +457,6 @@ trait MethodSynthesis { } case class Setter(tree: ValDef) extends DerivedSetter { def name = tree.setterName - def category = SetterTargetClass def flagsMask = SetterFlags def flagsExtra = ACCESSOR @@ -469,18 +476,14 @@ trait MethodSynthesis { // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`), // as the symbol info is in the process of being created then. // TODO: harmonize tree & symbol creation - // TODO: the `def field` call-site does not tollerate including `|| vd.symbol.owner.isTrait` --> tests break - def noFieldFor(vd: ValDef) = vd.mods.isDeferred || (vd.mods.isLazy && isUnitType(vd.symbol.info)) // || vd.symbol.owner.isTrait)) + // TODO: the `def field` call-site breaks when you add `|| vd.symbol.owner.isTrait` (detected in test suite) + def noFieldFor(vd: ValDef) = vd.mods.isDeferred || (vd.mods.isLazy && isUnitType(vd.symbol.info)) } case class Field(tree: ValDef) extends DerivedFromValDef { def name = tree.localName - def category = FieldTargetClass def flagsMask = FieldFlags def flagsExtra = PrivateLocal - // By default annotations go to the field, except if the field is - // generated for a class parameter (PARAMACCESSOR). - override def keepClean = !mods.isParamAccessor // handle lazy val first for now (we emit a Field even though we probably shouldn't...) override def derivedTree = @@ -491,10 +494,8 @@ trait MethodSynthesis { } case class Param(tree: ValDef) extends DerivedFromValDef { def name = tree.name - def category = ParamTargetClass def flagsMask = -1L def flagsExtra = 0L - override def keepClean = true override def derivedTree = EmptyTree } def validateParam(tree: ValDef) { @@ -508,7 +509,6 @@ trait MethodSynthesis { override def derivedSym = enclClass.info decl name } sealed trait AnyBeanGetter extends BeanAccessor with DerivedGetter { - def category = BeanGetterTargetClass override def validate() { if (derivedSym == NoSymbol) { // the namer decides whether to generate these symbols or not. at that point, we don't @@ -531,9 +531,7 @@ trait MethodSynthesis { } case class BooleanBeanGetter(tree: ValDef) extends BeanAccessor("is") with AnyBeanGetter { } case class BeanGetter(tree: ValDef) extends BeanAccessor("get") with AnyBeanGetter { } - case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter { - def category = BeanSetterTargetClass - } + case class BeanSetter(tree: ValDef) extends BeanAccessor("set") with DerivedSetter // No Symbols available. private def beanAccessorsFromNames(tree: ValDef) = { diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index e0d96df062..d237ccbb96 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -287,7 +287,7 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT val result = (localTyper.typedPos(tree.pos) { Select(Super(qual, tpnme.EMPTY) setPos qual.pos, sym.alias) }).asInstanceOf[Select] - debuglog("alias replacement: " + tree + " ==> " + result); //debug + debuglog(s"alias replacement: $sym --> ${sym.alias} / $tree ==> $result"); //debug localTyper.typed(gen.maybeMkAsInstanceOf(transformSuperSelect(result), sym.tpe, sym.alias.tpe, beforeRefChecks = true)) } else { /* |