diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/Mixin.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Mixin.scala | 1305 |
1 files changed, 324 insertions, 981 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index a079a76ce7..96e2135c52 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -1,5 +1,6 @@ /* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL + * Copyright 2005-2016 LAMP/EPFL and Lightbend, Inc + * * @author Martin Odersky */ @@ -8,110 +9,85 @@ package transform import symtab._ import Flags._ -import scala.collection.{ mutable, immutable } +import scala.annotation.tailrec +import scala.collection.mutable -abstract class Mixin extends InfoTransform with ast.TreeDSL { + +abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis { import global._ import definitions._ import CODE._ + /** The name of the phase: */ val phaseName: String = "mixin" - /** The phase might set the following new flags: */ - override def phaseNewFlags: Long = lateMODULE | notOVERRIDE + /** Some trait methods need to be implemented in subclasses, so they cannot be private. + * + * We used to publicize during explicitouter (for some reason), so the condition is a bit more involved now it's done here + * (need to exclude lambdaLIFTED methods, as they do no exist during explicitouter and thus did not need to be excluded...) + * + * They may be protected, now that traits are compiled 1:1 to interfaces. + * The same disclaimers about mapping Scala's notion of visibility to Java's apply: + * we cannot emit PROTECTED methods in interfaces on the JVM, + * but knowing that these trait methods are protected means we won't emit static forwarders. + * + * JVMS: "Methods of interfaces may have any of the flags in Table 4.6-A set + * except ACC_PROTECTED, ACC_FINAL, ACC_SYNCHRONIZED, and ACC_NATIVE (JLS ยง9.4)." + * + * TODO: can we just set the right flags from the start?? + * could we use the final flag to indicate a private method is really-really-private? + */ + def publicizeTraitMethod(sym: Symbol): Unit = { + if ((sym hasFlag PRIVATE) && !(sym hasFlag LIFTED) && ( // lambdalifted methods can remain private + // super accessors by definition must be implemented in a subclass, so can't be private + // TODO: why are they ever private in a trait to begin with!?!? (could just name mangle them to begin with) + // TODO: can we add the SYNTHESIZE_IMPL_IN_SUBCLASS flag to super accessors symbols? + (sym hasFlag SUPERACCESSOR) + // an accessor / module *may* need to be implemented in a subclass, and thus cannot be private + // TODO: document how we get here (lambdalift? fields has already made accessors not-private) + || (sym hasFlag ACCESSOR | MODULE) && (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS))) + sym.makeNotPrivate(sym.owner) + + // no need to make trait methods not-protected + // (we used to have to move them to another class when interfaces could not have concrete methods) + // see note in `synthFieldsAndAccessors` in Fields.scala + // if (sym hasFlag PROTECTED) sym setFlag notPROTECTED + } /** This map contains a binding (class -> info) if * the class with this info at phase mixinPhase has been treated for mixin composition */ private val treatedClassInfos = perRunCaches.newMap[Symbol, Type]() withDefaultValue NoType - /** Map a lazy, mixedin field accessor to its trait member accessor */ - private val initializer = perRunCaches.newMap[Symbol, Symbol]() // --------- helper functions ----------------------------------------------- /** A member of a trait is implemented statically if its implementation after the - * mixin transform is in the static implementation module. To be statically - * implemented, a member must be a method that belonged to the trait's implementation class + * mixin transform is RHS of the method body (destined to be in an interface default method) + * + * To be statically implemented, a member must be a method that belonged to the trait's implementation class * before (i.e. it is not abstract). Not statically implemented are * - non-private modules: these are implemented directly in the mixin composition class * (private modules, on the other hand, are implemented statically, but their * module variable is not. all such private modules are lifted, because * non-lifted private modules have been eliminated in ExplicitOuter) - * - field accessors and superaccessors, except for lazy value accessors which become initializer - * methods in the impl class (because they can have arbitrary initializers) + * - field accessors and superaccessors */ private def isImplementedStatically(sym: Symbol) = ( - sym.owner.isImplClass - && sym.isMethod + (sym.isMethod || ((sym hasFlag MODULE) && !sym.isStatic)) + // TODO: ^^^ non-static modules should have been turned into methods by fields by now, no? maybe the info transformer hasn't run??? + && notDeferred(sym) + && sym.owner.isTrait && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) - && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) - ) - - /** A member of a trait is static only if it belongs only to the - * implementation class, not the interface, and it is implemented - * statically. - */ - private def isStaticOnly(sym: Symbol) = - isImplementedStatically(sym) && sym.isImplOnly - - /** A member of a trait is forwarded if it is implemented statically and it - * is also visible in the trait's interface. In that case, a forwarder to - * the member's static implementation will be added to the class that - * inherits the trait. - */ - private def isForwarded(sym: Symbol) = - isImplementedStatically(sym) && !sym.isImplOnly - - /** Maps the type of an implementation class to its interface; - * maps all other types to themselves. - */ - private def toInterface(tp: Type): Type = - enteringMixin(tp.typeSymbol.toInterface).tpe - - private def isFieldWithBitmap(field: Symbol) = { - field.info // ensure that nested objects are transformed - // For checkinit consider normal value getters - // but for lazy values only take into account lazy getters - field.isLazy && field.isMethod && !field.isDeferred - } - - /** Does this field require an initialized bit? - * Note: fields of classes inheriting DelayedInit are not checked. - * This is because they are neither initialized in the constructor - * nor do they have a setter (not if they are vals anyway). The usual - * logic for setting bitmaps does therefore not work for such fields. - * That's why they are excluded. - * Note: The `checkinit` option does not check if transient fields are initialized. - */ - private def needsInitFlag(sym: Symbol) = ( - settings.checkInit - && sym.isGetter - && !sym.isInitializedToDefault - && !isConstantType(sym.info.finalResultType) // SI-4742 - && !sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY) - && !sym.accessed.hasFlag(PRESUPER) - && !sym.isOuterAccessor - && !(sym.owner isSubClass DelayedInitClass) - && !(sym.accessed hasAnnotation TransientAttr) + && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || (sym hasFlag LAZY)) + && !sym.isPrivate + && !sym.hasAllFlags(LIFTED | MODULE | METHOD) + && !sym.isConstructor + && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE)) ) - /** Maps all parts of this type that refer to implementation classes to - * their corresponding interfaces. - */ - private val toInterfaceMap = new TypeMap { - def apply(tp: Type): Type = mapOver( tp match { - case TypeRef(pre, sym, args) if sym.isImplClass => - typeRef(pre, enteringMixin(sym.toInterface), args) - case _ => tp - }) - } - /** The implementation class corresponding to a currently compiled interface. - * todo: try to use Symbol.implClass instead? - */ - private def implClass(iface: Symbol) = iface.implClass orElse (erasure implClass iface) /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * @@ -139,16 +115,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // --------- type transformation ----------------------------------------------- - def isConcreteAccessor(member: Symbol) = - member.hasAccessorFlag && (!member.isDeferred || (member hasFlag lateDEFERRED)) + @inline final def notDeferred(sym: Symbol) = fields.notDeferredOrSynthImpl(sym) /** Is member overridden (either directly or via a bridge) in base class sequence `bcs`? */ def isOverriddenAccessor(member: Symbol, bcs: List[Symbol]): Boolean = beforeOwnPhase { def hasOverridingAccessor(clazz: Symbol) = { clazz.info.nonPrivateDecl(member.name).alternatives.exists( sym => - isConcreteAccessor(sym) && + sym.hasFlag(ACCESSOR) && !sym.hasFlag(MIXEDIN) && + notDeferred(sym) && matchesType(sym.tpe, member.tpe, alwaysMatchSimple = true)) } ( bcs.head != member.owner @@ -156,11 +132,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { ) } + /** Add given member to given class, and mark member as mixed-in. */ def addMember(clazz: Symbol, member: Symbol): Symbol = { - debuglog("new member of " + clazz + ":" + member.defString) - clazz.info.decls enter member setFlag MIXEDIN + debuglog(s"mixing into $clazz: ${member.defString}") + // This attachment is used to instruct the backend about which methods in traits require + // a static trait impl method. We remove this from the new symbol created for the method + // mixed into the subclass. + member.removeAttachment[NeedStaticImpl.type] + clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD } def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz)) @@ -191,57 +172,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { newSym } - /** Add getters and setters for all non-module fields of an implementation - * class to its interface unless they are already present. This is done - * only once per class. The mixedin flag is used to remember whether late - * members have been added to an interface. - * - lazy fields don't get a setter. - */ - def addLateInterfaceMembers(clazz: Symbol) { + def publicizeTraitMethods(clazz: Symbol) { if (treatedClassInfos(clazz) != clazz.info) { treatedClassInfos(clazz) = clazz.info assert(phase == currentRun.mixinPhase, phase) - /* Create a new getter. Getters are never private or local. They are - * always accessors and deferred. */ - def newGetter(field: Symbol): Symbol = { - // println("creating new getter for "+ field +" : "+ field.info +" at "+ field.locationString+(field hasFlag MUTABLE)) - val newFlags = field.flags & ~PrivateLocal | ACCESSOR | lateDEFERRED | ( if (field.isMutable) 0 else STABLE ) - // TODO preserve pre-erasure info? - clazz.newMethod(field.getterName, field.pos, newFlags) setInfo MethodType(Nil, field.info) - } - - /* Create a new setter. Setters are never private or local. They are - * always accessors and deferred. */ - def newSetter(field: Symbol): Symbol = { - //println("creating new setter for "+field+field.locationString+(field hasFlag MUTABLE)) - val setterName = field.setterName - val newFlags = field.flags & ~PrivateLocal | ACCESSOR | lateDEFERRED - val setter = clazz.newMethod(setterName, field.pos, newFlags) - // TODO preserve pre-erasure info? - setter setInfo MethodType(setter.newSyntheticValueParams(List(field.info)), UnitTpe) - if (field.needsExpandedSetterName) - setter.name = nme.expandedSetterName(setter.name, clazz) - - setter - } - - clazz.info // make sure info is up to date, so that implClass is set. - val impl = implClass(clazz) orElse abort("No impl class for " + clazz) - - for (member <- impl.info.decls) { - if (!member.isMethod && !member.isModule && !member.isModuleVar) { + for (member <- clazz.info.decls) { + if (member.isMethod) publicizeTraitMethod(member) + else { assert(member.isTerm && !member.isDeferred, member) - if (member.getterIn(impl).isPrivate) { - member.makeNotPrivate(clazz) // this will also make getter&setter not private - } - val getter = member.getterIn(clazz) - if (getter == NoSymbol) addMember(clazz, newGetter(member)) - if (!member.tpe.isInstanceOf[ConstantType] && !member.isLazy) { - val setter = member.setterIn(clazz) - if (setter == NoSymbol) addMember(clazz, newSetter(member)) - } + // disable assert to support compiling against code compiled by an older compiler (until we re-starr) + // assert(member hasFlag PRESUPER, s"unexpected $member in $clazz ${member.debugFlagString}") + clazz.info.decls.unlink(member) } + } debuglog("new defs of " + clazz + " = " + clazz.info.decls) } @@ -262,75 +206,83 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { def cloneAndAddMixinMember(mixinClass: Symbol, mixinMember: Symbol): Symbol = ( cloneAndAddMember(mixinClass, mixinMember, clazz) setPos clazz.pos - resetFlag DEFERRED | lateDEFERRED + resetFlag DEFERRED ) /* Mix in members of implementation class mixinClass into class clazz */ - def mixinImplClassMembers(mixinClass: Symbol, mixinInterface: Symbol) { - if (!mixinClass.isImplClass) devWarning ("Impl class flag is not set " + - ((mixinClass.debugLocationString, mixinInterface.debugLocationString))) - - for (member <- mixinClass.info.decls ; if isForwarded(member)) { - val imember = member overriddenSymbol mixinInterface - imember overridingSymbol clazz match { + def mixinTraitForwarders(mixinClass: Symbol) { + for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) { + member overridingSymbol clazz match { case NoSymbol => - if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember) - cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member + val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) + if (isMemberOfClazz) { + def genForwarder(required: Boolean): Unit = { + val owner = member.owner + if (owner.isJavaDefined && owner.isInterface && !clazz.parentSymbols.contains(owner)) { + if (required) { + val text = s"Unable to implement a mixin forwarder for $member in $clazz unless interface ${owner.name} is directly extended by $clazz." + reporter.error(clazz.pos, text) + } + } else + cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + } + + // `member` is a concrete method defined in `mixinClass`, which is a base class of + // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: + // + // - A non-trait base class of `clazz` defines a matching method. Example: + // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T + // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would + // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. + // + // - There exists another concrete, matching method in a parent interface `p` of + // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the + // forwarder is needed to disambiguate. Example: + // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 + // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves + // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". + // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 + // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM + // level. + + @tailrec + def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { + case baseClass :: rest => + if (baseClass ne mixinClass) { + val m = member.overriddenSymbol(baseClass) + val isCompeting = m.exists && { + !m.owner.isTraitOrInterface || + (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) + } + isCompeting || existsCompetingMethod(rest) + } else existsCompetingMethod(rest) + + case _ => false + } + + def generateJUnitForwarder: Boolean = { + settings.mixinForwarderChoices.isAtLeastJunit && + member.annotations.nonEmpty && + JUnitAnnotations.exists(annot => annot.exists && member.hasAnnotation(annot)) + } + + if (existsCompetingMethod(clazz.baseClasses) || generateJUnitForwarder) + genForwarder(required = true) + else if (settings.mixinForwarderChoices.isTruthy) + genForwarder(required = false) + } + case _ => } } } - /* Mix in members of trait mixinClass into class clazz. Also, - * for each lazy field in mixinClass, add a link from its mixed in member to its - * initializer method inside the implclass. + /* Mix in members of trait mixinClass into class clazz. */ def mixinTraitMembers(mixinClass: Symbol) { // For all members of a trait's interface do: for (mixinMember <- mixinClass.info.decls) { - if (isConcreteAccessor(mixinMember)) { - if (isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) - devWarning(s"Overridden concrete accessor: ${mixinMember.fullLocationString}") - else { - // mixin field accessors - val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) - if (mixinMember.isLazy) { - initializer(mixedInAccessor) = ( - implClass(mixinClass).info.decl(mixinMember.name) - orElse abort("Could not find initializer for " + mixinMember.name) - ) - } - if (!mixinMember.isSetter) - mixinMember.tpe match { - case MethodType(Nil, ConstantType(_)) => - // mixinMember is a constant; only getter is needed - ; - case MethodType(Nil, TypeRef(_, UnitClass, _)) => - // mixinMember is a value of type unit. No field needed - ; - case _ => // otherwise mixin a field as well - // enteringPhase: the private field is moved to the implementation class by erasure, - // so it can no longer be found in the mixinMember's owner (the trait) - val accessed = enteringPickler(mixinMember.accessed) - // #3857, need to retain info before erasure when cloning (since cloning only - // carries over the current entry in the type history) - val sym = enteringErasure { - // so we have a type history entry before erasure - clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType) - } - sym updateInfo mixinMember.tpe.resultType // info at current phase - - val newFlags = ( - ( PrivateLocal ) - | ( mixinMember getFlag MUTABLE | LAZY) - | ( if (mixinMember.hasStableFlag) 0 else MUTABLE ) - ) - - addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations) - } - } - } - else if (mixinMember.isSuperAccessor) { // mixin super accessors + if (mixinMember.hasFlag(SUPERACCESSOR)) { // mixin super accessors val superAccessor = addMember(clazz, mixinMember.cloneSymbol(clazz)) setPos clazz.pos assert(superAccessor.alias != NoSymbol, superAccessor) @@ -339,12 +291,42 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { reporter.error(clazz.pos, "Member %s of mixin %s is missing a concrete super implementation.".format( mixinMember.alias, mixinClass)) case alias1 => + if (alias1.owner.isJavaDefined && alias1.owner.isInterface && !clazz.parentSymbols.contains(alias1.owner)) { + val suggestedParent = exitingTyper(clazz.info.baseType(alias1.owner)) + reporter.error(clazz.pos, s"Unable to implement a super accessor required by trait ${mixinClass.name} unless $suggestedParent is directly extended by $clazz.") + } superAccessor.asInstanceOf[TermSymbol] setAlias alias1 } } - else if (mixinMember.isMethod && mixinMember.isModule && mixinMember.hasNoFlags(LIFTED | BRIDGE)) { - // mixin objects: todo what happens with abstract objects? - addMember(clazz, mixinMember.cloneSymbol(clazz, mixinMember.flags & ~(DEFERRED | lateDEFERRED)) setPos clazz.pos) + else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember) + && (mixinMember hasFlag PARAMACCESSOR) + && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { + // mixin accessor for constructor parameter + // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) + cloneAndAddMixinMember(mixinClass, mixinMember) + + val name = mixinMember.name + + if (!nme.isSetterName(name)) { + // enteringPhase: the private field is moved to the implementation class by erasure, + // so it can no longer be found in the mixinMember's owner (the trait) + val accessed = enteringPickler(mixinMember.accessed) + // #3857, need to retain info before erasure when cloning (since cloning only + // carries over the current entry in the type history) + val sym = enteringErasure { + // so we have a type history entry before erasure + clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType) + } + sym updateInfo mixinMember.tpe.resultType // info at current phase + + val newFlags = ( + (PrivateLocal) + | (mixinMember getFlag MUTABLE) + | (if (mixinMember.hasStableFlag) 0 else MUTABLE) + ) + + addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations) + } } } } @@ -358,162 +340,38 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // first complete the superclass with mixed in members addMixedinMembers(clazz.superClass, unit) - for (mc <- clazz.mixinClasses ; if mc hasFlag lateINTERFACE) { + for (mc <- clazz.mixinClasses ; if mc.isTrait) { // @SEAN: adding trait tracking so we don't have to recompile transitive closures unit.depends += mc - addLateInterfaceMembers(mc) + publicizeTraitMethods(mc) mixinTraitMembers(mc) - mixinImplClassMembers(implClass(mc), mc) + mixinTraitForwarders(mc) } } - /** The info transform for this phase does the following: - * - The parents of every class are mapped from implementation class to interface - * - Implementation classes become modules that inherit nothing - * and that define all. - */ - override def transformInfo(sym: Symbol, tp: Type): Type = tp match { - case ClassInfoType(parents, decls, clazz) => - var parents1 = parents - var decls1 = decls - if (!clazz.isPackageClass) { - exitingMixin(clazz.owner.info) - if (clazz.isImplClass) { - clazz setFlag lateMODULE - var sourceModule = clazz.owner.info.decls.lookup(sym.name.toTermName) - if (sourceModule == NoSymbol) { - sourceModule = ( - clazz.owner.newModuleSymbol(sym.name.toTermName, sym.pos, MODULE) - setModuleClass sym.asInstanceOf[ClassSymbol] - ) - clazz.owner.info.decls enter sourceModule - } - else { - sourceModule setPos sym.pos - if (sourceModule.flags != MODULE) { - log(s"!!! Directly setting sourceModule flags for $sourceModule from ${sourceModule.flagString} to MODULE") - sourceModule.flags = MODULE - } - } - sourceModule setInfo sym.tpe - // Companion module isn't visible for anonymous class at this point anyway - assert(clazz.sourceModule != NoSymbol || clazz.isAnonymousClass, s"$clazz has no sourceModule: $sym ${sym.tpe}") - parents1 = List() - decls1 = newScopeWith(decls.toList filter isImplementedStatically: _*) - } else if (!parents.isEmpty) { - parents1 = parents.head :: (parents.tail map toInterface) - } - } - //decls1 = enteringPhase(phase.next)(newScopeWith(decls1.toList: _*))//debug - if ((parents1 eq parents) && (decls1 eq decls)) tp - else ClassInfoType(parents1, decls1, clazz) - - case MethodType(params, restp) => - toInterfaceMap( - if (isImplementedStatically(sym)) { - val ownerParam = sym.newSyntheticValueParam(toInterface(sym.owner.typeOfThis)) - MethodType(ownerParam :: params, restp) - } else - tp) - - case _ => - tp - } - - /** Return a map of single-use fields to the lazy value that uses them during initialization. - * Each field has to be private and defined in the enclosing class, and there must - * be exactly one lazy value using it. - * - * Such fields will be nulled after the initializer has memoized the lazy value. - */ - def singleUseFields(templ: Template): scala.collection.Map[Symbol, List[Symbol]] = { - val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil - - object SingleUseTraverser extends Traverser { - override def traverse(tree: Tree) { - tree match { - case Assign(lhs, rhs) => traverse(rhs) // assignments don't count - case _ => - if (tree.hasSymbolField && tree.symbol != NoSymbol) { - val sym = tree.symbol - if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) - && sym.isPrivate - && !(currentOwner.isGetter && currentOwner.accessed == sym) // getter - && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) - && sym.owner == templ.symbol.owner - && !sym.isLazy - && !tree.isDef) { - debuglog("added use in: " + currentOwner + " -- " + tree) - usedIn(sym) ::= currentOwner - - } - } - super.traverse(tree) - } - } - } - SingleUseTraverser(templ) - debuglog("usedIn: " + usedIn) - usedIn filter { - case (_, member :: Nil) => member.isValue && member.isLazy - case _ => false - } - } + override def transformInfo(sym: Symbol, tp: Type): Type = tp // --------- term transformation ----------------------------------------------- protected def newTransformer(unit: CompilationUnit): Transformer = new MixinTransformer(unit) - class MixinTransformer(unit : CompilationUnit) extends Transformer { - /** Within a static implementation method: the parameter referring to the - * current object. Undefined everywhere else. - */ - private var self: Symbol = _ + class MixinTransformer(unit : CompilationUnit) extends Transformer with AccessorTreeSynthesis { + /** The typer */ + private var localTyper: erasure.Typer = _ + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) /** The rootContext used for typing */ private val rootContext = erasure.NoContext.make(EmptyTree, rootMirror.RootClass, newScope) - /** The typer */ - private var localTyper: erasure.Typer = _ - private def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) - - /** Map lazy values to the fields they should null after initialization. */ - private var lazyValNullables: Map[Symbol, Set[Symbol]] = _ - - /** Map a field symbol to a unique integer denoting its position in the class layout. - * For each class, fields defined by the class come after inherited fields. Mixed-in - * fields count as fields defined by the class itself. - */ - private val fieldOffset = perRunCaches.newMap[Symbol, Int]() - - private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() - - // ByteClass, IntClass, LongClass - private def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) - - private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { - case BooleanClass => 1 - case ByteClass => 8 - case IntClass => 32 - case LongClass => 64 - } - + private val nullables = mutable.AnyRefMap[Symbol, Map[Symbol, List[Symbol]]]() /** The first transform; called in a pre-order traversal at phase mixin * (that is, every node is processed before its children). * What transform does: * - For every non-trait class, add all mixed in members to the class info. - * - For every trait, add all late interface members to the class info - * - For every static implementation method: - * - remove override flag - * - create a new method definition that also has a `self` parameter - * (which comes first) Iuli: this position is assumed by tail call elimination - * on a different receiver. Storing a new 'this' assumes it is located at - * index 0 in the local variable table. See 'STORE_THIS' and GenASM. - * - Map implementation class types in type-apply's to their interfaces - * - Remove all fields in implementation classes + * - For every non-trait class, assign null to singly used private fields after use in lazy initialization. */ private def preTransform(tree: Tree): Tree = { val sym = tree.symbol @@ -524,617 +382,160 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (!currentOwner.isTrait && !isPrimitiveValueClass(currentOwner)) addMixedinMembers(currentOwner, unit) - else if (currentOwner hasFlag lateINTERFACE) - addLateInterfaceMembers(currentOwner) + else if (currentOwner.isTrait) + publicizeTraitMethods(currentOwner) + + if (!currentOwner.isTrait) + nullables(currentOwner) = lazyValNullables(currentOwner, body) tree - case DefDef(_, _, _, vparams :: Nil, _, _) => - if (currentOwner.isImplClass) { - if (isImplementedStatically(sym)) { - sym setFlag notOVERRIDE - self = sym.newValueParameter(nme.SELF, sym.pos) setInfo toInterface(currentOwner.typeOfThis) - val selfdef = ValDef(self) setType NoType - copyDefDef(tree)(vparamss = List(selfdef :: vparams)) - } - else EmptyTree - } + case dd: DefDef if dd.symbol.name.endsWith(nme.LAZY_SLOW_SUFFIX) => + val fieldsToNull = nullables.getOrElse(sym.enclClass, Map()).getOrElse(sym, Nil) + if (fieldsToNull.isEmpty) dd else { - if (currentOwner.isTrait && sym.isSetter && !enteringPickler(sym.isDeferred)) { - sym.addAnnotation(TraitSetterAnnotationClass) + deriveDefDef(dd) { + case blk@Block(stats, expr) => + assert(dd.symbol.originalOwner.isClass, dd.symbol) + def nullify(sym: Symbol) = + Select(gen.mkAttributedThis(sym.enclClass), sym.accessedOrSelf) === NULL + val stats1 = stats ::: fieldsToNull.map(nullify) + treeCopy.Block(blk, stats1, expr) + case tree => + devWarning("Unexpected tree shape in lazy slow path") + tree } - tree - } - // !!! What is this doing, and why is it only looking for exactly - // one type parameter? It would seem to be - // "Map implementation class types in type-apply's to their interfaces" - // from the comment on preTransform, but is there some way we should know - // that impl class types in type applies can only appear in single - // type parameter type constructors? - case Apply(tapp @ TypeApply(fn, List(arg)), List()) => - if (arg.tpe.typeSymbol.isImplClass) { - val ifacetpe = toInterface(arg.tpe) - arg setType ifacetpe - tapp setType MethodType(Nil, ifacetpe) - tree setType ifacetpe } - tree - case ValDef(_, _, _, _) if currentOwner.isImplClass => - EmptyTree - case _ => - tree - } - } - /** Create an identifier which references self parameter. - */ - private def selfRef(pos: Position) = - gen.mkAttributedIdent(self) setPos pos - - /** Replace a super reference by this or the self parameter, depending - * on whether we are in an implementation class or not. - * Leave all other trees unchanged. - */ - private def transformSuper(tree: Tree) = tree match { - case Super(qual, _) => - transformThis(qual) - case _ => - tree - } - - /** Replace a this reference to the current implementation class by the self - * parameter. Leave all other trees unchanged. - */ - private def transformThis(tree: Tree) = tree match { - case This(_) if tree.symbol.isImplClass => - assert(tree.symbol == currentOwner.enclClass) - selfRef(tree.pos) - case _ => - tree - } - - /** Create a static reference to given symbol `sym` of the - * form `M.sym` where M is the symbol's implementation module. - */ - private def staticRef(sym: Symbol): Tree = { - sym.owner.info //todo: needed? - sym.owner.owner.info //todo: needed? - - if (sym.owner.sourceModule eq NoSymbol) - abort(s"Cannot create static reference to $sym because ${sym.safeOwner} has no source module") - else - REF(sym.owner.sourceModule) DOT sym - } - - def needsInitAndHasOffset(sym: Symbol) = - needsInitFlag(sym) && (fieldOffset contains sym) - - /** Examines the symbol and returns a name indicating what brand of - * bitmap it requires. The possibilities are the BITMAP_* vals - * defined in StdNames. If it needs no bitmap, nme.NO_NAME. - */ - def bitmapCategory(field: Symbol): Name = { - import nme._ - val isNormal = ( - if (isFieldWithBitmap(field)) true - // bitmaps for checkinit fields are not inherited - else if (needsInitFlag(field) && !field.isDeferred) false - else return NO_NAME - ) - if (field.accessed hasAnnotation TransientAttr) { - if (isNormal) BITMAP_TRANSIENT - else BITMAP_CHECKINIT_TRANSIENT - } else { - if (isNormal) BITMAP_NORMAL - else BITMAP_CHECKINIT + case _ => tree } } - /** Add all new definitions to a non-trait class - * These fall into the following categories: - * - for a trait interface: - * - abstract accessors for all fields in the implementation class - * - for a non-trait class: - * - A field for every in a mixin class - * - Setters and getters for such fields - * - getters for mixed in lazy fields are completed - * - module variables and module creators for every module in a mixin class - * (except if module is lifted -- in this case the module variable - * is local to some function, and the creator method is static.) - * - A super accessor for every super accessor in a mixin class - * - Forwarders for all methods that are implemented statically - * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) - * @param clazz The class to which definitions are added - */ - private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { - val newDefs = mutable.ListBuffer[Tree]() - - /* Attribute given tree and anchor at given position */ - def attributedDef(pos: Position, tree: Tree): Tree = { - debuglog("add new def to " + clazz + ": " + tree) - typedPos(pos)(tree) - } - - /* The position of given symbol, or, if this is undefined, - * the position of the current class. - */ - def position(sym: Symbol) = - if (sym.pos == NoPosition) clazz.pos else sym.pos - - /* Add tree at given position as new definition */ - def addDef(pos: Position, tree: Tree) { - newDefs += attributedDef(pos, tree) - } - - /* Add new method definition. - * - * @param sym The method symbol. - * @param rhs The method body. - */ - def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), DefDef(sym, rhs)) - def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), ValDef(sym, rhs)) - - /* Add `newdefs` to `stats`, removing any abstract method definitions - * in `stats` that are matched by some symbol defined in - * `newDefs`. - */ - def add(stats: List[Tree], newDefs: List[Tree]) = { - val newSyms = newDefs map (_.symbol) - def isNotDuplicate(tree: Tree) = tree match { - case DefDef(_, _, _, _, _, _) => - val sym = tree.symbol - !(sym.isDeferred && - (newSyms exists (nsym => nsym.name == sym.name && (nsym.tpe matches sym.tpe)))) - case _ => - true - } - if (newDefs.isEmpty) stats - else newDefs ::: (stats filter isNotDuplicate) - } - - /* If `stat` is a superaccessor, complete it by adding a right-hand side. - * Note: superaccessors are always abstract until this point. - * The method to call in a superaccessor is stored in the accessor symbol's alias field. - * The rhs is: - * super.A(xs) where A is the super accessor's alias and xs are its formal parameters. - * This rhs is typed and then mixin transformed. - */ - def completeSuperAccessor(stat: Tree) = stat match { - case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor => - val body = atPos(stat.pos)(Apply(Select(Super(clazz, tpnme.EMPTY), stat.symbol.alias), vparams map (v => Ident(v.symbol)))) - val pt = stat.symbol.tpe.resultType - - copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt)))) - case _ => - stat - } - - /* - * Return the bitmap field for 'offset'. Depending on the hierarchy it is possible to reuse - * the bitmap of its parents. If that does not exist yet we create one. - */ - def bitmapFor(clazz0: Symbol, offset: Int, field: Symbol): Symbol = { - val category = bitmapCategory(field) - val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName - val sym = clazz0.info.decl(bitmapName) - - assert(!sym.isOverloaded, sym) - - def createBitmap: Symbol = { - val bitmapKind = bitmapKindForCategory(category) - val sym = clazz0.newVariable(bitmapName, clazz0.pos) setInfo bitmapKind.tpe - enteringTyper(sym addAnnotation VolatileAttr) - - category match { - case nme.BITMAP_TRANSIENT | nme.BITMAP_CHECKINIT_TRANSIENT => sym addAnnotation TransientAttr - case _ => - } - val init = bitmapKind match { - case BooleanClass => ValDef(sym, FALSE) - case _ => ValDef(sym, ZERO) + /** Map lazy values to the fields they should null after initialization. */ + def lazyValNullables(clazz: Symbol, templStats: List[Tree]): Map[Symbol, List[Symbol]] = { + // if there are no lazy fields, take the fast path and save a traversal of the whole AST + if (!clazz.info.decls.exists(_.isLazy)) Map() + else { + // A map of single-use fields to the lazy value that uses them during initialization. + // Each field has to be private and defined in the enclosing class, and there must + // be exactly one lazy value using it. + // + // Such fields will be nulled after the initializer has memoized the lazy value. + val singleUseFields: Map[Symbol, List[Symbol]] = { + val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil + + object SingleUseTraverser extends Traverser { + override def traverse(tree: Tree) { + tree match { + // assignment targets don't count as a dereference -- only check the rhs + case Assign(_, rhs) => traverse(rhs) + case tree: RefTree if tree.symbol != NoSymbol => + val sym = tree.symbol + // println(s"$sym in ${sym.owner} from $currentOwner ($tree)") + if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) && sym.isPrivate && !sym.isLazy && !sym.isModule // non-lazy private field or its accessor + && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) // primitives don't hang on to significant amounts of heap + && sym.owner == currentOwner.enclClass && !(currentOwner.isGetter && currentOwner.accessed == sym)) { + + // println("added use in: " + currentOwner + " -- " + tree) + usedIn(sym) ::= currentOwner + } + super.traverse(tree) + case _ => super.traverse(tree) + } + } } - - sym setFlag PrivateLocal - clazz0.info.decls.enter(sym) - addDef(clazz0.pos, init) - sym + templStats foreach SingleUseTraverser.apply + // println("usedIn: " + usedIn) + + // only consider usages from non-transient lazy vals (SI-9365) + val singlyUsedIn = usedIn.filter { + case (_, member :: Nil) if member.name.endsWith(nme.LAZY_SLOW_SUFFIX) => + val lazyAccessor = member.owner.info.decl(member.name.stripSuffix(nme.LAZY_SLOW_SUFFIX)) + !lazyAccessor.accessedOrSelf.hasAnnotation(TransientAttr) + case _ => false + }.toMap + + // println("singlyUsedIn: " + singlyUsedIn) + singlyUsedIn } - sym orElse createBitmap - } - - def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = { - def realOffset = offset % flagsPerBitmap(sym) - if (kind == LongClass ) LIT(1L << realOffset) else LIT(1 << realOffset) - } - - /* Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ - def mkSetFlag(clazz: Symbol, offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { - val bmp = bitmapFor(clazz, offset, valSym) - def mask = maskForOffset(offset, valSym, kind) - def x = This(clazz) DOT bmp - def newValue = if (kind == BooleanClass) TRUE else (x GEN_| (mask, kind)) - - x === newValue - } - - /* Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the - * precise comparison operator depending on the value of 'equalToZero'. - */ - def mkTest(clazz: Symbol, mask: Tree, bitmapSym: Symbol, equalToZero: Boolean, kind: ClassSymbol): Tree = { - val bitmapTree = (This(clazz) DOT bitmapSym) - def lhs = bitmapTree GEN_& (mask, kind) - kind match { - case BooleanClass => - if (equalToZero) NOT(bitmapTree) - else bitmapTree - case _ => - if (equalToZero) lhs GEN_== (ZERO, kind) - else lhs GEN_!= (ZERO, kind) - } - } - - def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Symbol = { - val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, PRIVATE) - val params = defSym newSyntheticValueParams args.map(_.symbol.tpe) - defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType) - val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym) - val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params) - addDef(position(defSym), DefDef(defSym, strictSubst(BLOCK(rhs, retVal)))) - defSym - } - - def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): Tree = { - mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List()) - } - - def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = { - val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args) - If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal) - } - - - /* Always copy the tree if we are going to perform sym substitution, - * otherwise we will side-effect on the tree that is used in the fast path - */ - class TreeSymSubstituterWithCopying(from: List[Symbol], to: List[Symbol]) extends TreeSymSubstituter(from, to) { - override def transform(tree: Tree): Tree = - if (tree.hasSymbolField && from.contains(tree.symbol)) - super.transform(tree.duplicate) - else super.transform(tree.duplicate) - - override def apply[T <: Tree](tree: T): T = if (from.isEmpty) tree else super.apply(tree) - } - - /* return a 'lazified' version of rhs. It uses double-checked locking to ensure - * initialization is performed at most once. For performance reasons the double-checked - * locking is split into two parts, the first (fast) path checks the bitmap without - * synchronizing, and if that fails it initializes the lazy val within the - * synchronization block (slow path). This way the inliner should optimize - * the fast path because the method body is small enough. - * Private fields used only in this initializer are subsequently set to null. - * - * @param clazz The class symbol - * @param lzyVal The symbol of this lazy field - * @param init The tree which initializes the field ( f = <rhs> ) - * @param offset The offset of this field in the flags bitmap - * - * The result will be a tree of the form - * { if ((bitmap&n & MASK) == 0) this.l$compute() - * else l$ - * - * ... - * def l$compute() = { synchronized(this) { - * if ((bitmap$n & MASK) == 0) { - * init // l$ = <rhs> - * bitmap$n = bimap$n | MASK - * }} - * l$ - * } - * - * ... - * this.f1 = null - * ... this.fn = null - * } - * where bitmap$n is a byte, int or long value acting as a bitmap of initialized values. - * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it. - * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32), - * the MASK is (1 << (offset % 32)). - * If the class contains only a single lazy val then the bitmap is represented - * as a Boolean and the condition checking is a simple bool test. - */ - def mkLazyDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = { - def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) - - val bitmapSym = bitmapFor(clazz, offset, lzyVal) - val kind = bitmapKind(lzyVal) - val mask = maskForOffset(offset, lzyVal, kind) - def cond = mkTest(clazz, mask, bitmapSym, equalToZero = true, kind) - val nulls = lazyValNullables(lzyVal).toList sortBy (_.id) map nullify - def syncBody = init ::: List(mkSetFlag(clazz, offset, lzyVal, kind), UNIT) - - if (nulls.nonEmpty) - log("nulling fields inside " + lzyVal + ": " + nulls) - - typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal)) - } - - def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree = - rhs match { - case Block(List(assign), returnTree) => - val Assign(moduleVarRef, _) = assign - val cond = Apply(Select(moduleVarRef, Object_eq), List(NULL)) - mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args) - case _ => - abort(s"Invalid getter $rhs for module in $clazz") - } + val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() + // invert the map to see which fields can be nulled for each non-transient lazy val + for ((field, users) <- singleUseFields; lazyFld <- users) map(lazyFld) += field - def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { - val sym = fieldSym.getterIn(fieldSym.owner) - val bitmapSym = bitmapFor(clazz, offset, sym) - val kind = bitmapKind(sym) - val mask = maskForOffset(offset, sym, kind) - val msg = s"Uninitialized field: ${unit.source}: ${pos.line}" - val result = - IF (mkTest(clazz, mask, bitmapSym, equalToZero = false, kind)) . - THEN (retVal) . - ELSE (Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) - - typedPos(pos)(BLOCK(result, retVal)) + map.mapValues(_.toList sortBy (_.id)).toMap } + } - /* Complete lazy field accessors. Applies only to classes, - * for its own (non inherited) lazy fields. If 'checkinit' - * is enabled, getters that check for the initialized bit are - * generated, and the class constructor is changed to set the - * initialized bits. - */ - def addCheckedGetters(clazz: Symbol, stats: List[Tree]): List[Tree] = { - def dd(stat: DefDef) = { - val sym = stat.symbol - def isUnit = sym.tpe.resultType.typeSymbol == UnitClass - def isEmpty = stat.rhs == EmptyTree - - if (sym.isLazy && !isEmpty && !clazz.isImplClass) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnit => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) - - case Block(stats, res) => - mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) - - case t => t // pass specialized lazy vals through - } - } - else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(IMPLCLASS | TRAIT)) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat)(rhs => - (mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))( - if (sym.tpe.resultType.typeSymbol == UnitClass) UNIT - else rhs - ) - ) - } - else if (sym.isConstructor) { - deriveDefDef(stat)(addInitBits(clazz, _)) - } - else if (settings.checkInit && !clazz.isTrait && sym.isSetter) { - val getter = sym.getterIn(clazz) - if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) - deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) - else stat - } - else if (sym.isModule && (!clazz.isTrait || clazz.isImplClass) && !sym.isBridge) { - deriveDefDef(stat)(rhs => - typedPos(stat.pos)( - mkInnerClassAccessorDoubleChecked( - // Martin to Hubert: I think this can be replaced by selfRef(tree.pos) - // @PP: It does not seem so, it crashes for me trying to bootstrap. - if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz), - rhs, sym, stat.vparamss.head - ) - ) - ) - } - else stat - } - stats map { - case defn: DefDef => dd(defn) - case stat => stat - } - } + /** Add all new definitions to a non-trait class + * + * These fall into the following categories: + * - for a trait interface: + * - abstract accessors for all paramaccessor or early initialized fields + * - for a non-trait class: + * - field and accessor implementations for each inherited paramaccessor or early initialized field + * - A super accessor for every super accessor in a mixin class + * - Forwarders for all methods that are implemented statically + * + * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) + * + * @param clazz The class to which definitions are added + */ + private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { + val accessorSynth = new UncheckedAccessorSynth(clazz) + import accessorSynth._ - class AddInitBitsTransformer(clazz: Symbol) extends Transformer { - private def checkedGetter(lhs: Tree) = { - val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitAndHasOffset(sym)) { - debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym, bitmapKind(sym))) - } - else Nil + // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers + for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { + // if current class is a trait, add an abstract method for accessor `sym` + // ditto for a super accessor (will get an RHS in completeSuperAccessor) + if (clazz.isTrait || sym.isSuperAccessor) addDefDef(sym) + // implement methods mixed in from a supertrait (the symbols were created by mixinTraitMembers) + else if (sym.hasFlag(ACCESSOR) && !sym.hasFlag(DEFERRED)) { + assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not param?!?") + + // add accessor definitions + addDefDef(sym, accessorBody(sym)) } - override def transformStats(stats: List[Tree], exprOwner: Symbol) = { - // !!! Ident(self) is never referenced, is it supposed to be confirming - // that self is anything in particular? - super.transformStats( - stats flatMap { - case stat @ Assign(lhs @ Select(This(_), _), rhs) => stat :: checkedGetter(lhs) - // remove initialization for default values - case Apply(lhs @ Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil - case stat => List(stat) - }, - exprOwner - ) + else if (!sym.isMethod) addValDef(sym) // field + else if (!sym.isMacro) { // forwarder + assert(sym.alias != NoSymbol, (sym, sym.debugFlagString, clazz)) + // debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString) + addDefDef(sym, Apply(SuperSelect(clazz, sym.alias), sym.paramss.head.map(Ident(_)))) } } - /* Adds statements to set the 'init' bit for each field initialized - * in the body of a constructor. - */ - def addInitBits(clazz: Symbol, rhs: Tree): Tree = - new AddInitBitsTransformer(clazz) transform rhs - - // begin addNewDefs - - /* Fill the map from fields to offset numbers. - * Instead of field symbols, the map keeps their getter symbols. This makes - * code generation easier later. - */ - def buildBitmapOffsets() { - def fold(fields: List[Symbol], category: Name) = { - var idx = 0 - fields foreach { f => - fieldOffset(f) = idx - idx += 1 - } + val implementedAccessors = implementWithNewDefs(stats) - if (idx == 0) () - else if (idx == 1) bitmapKindForCategory(category) = BooleanClass - else if (idx < 9) bitmapKindForCategory(category) = ByteClass - else if (idx < 33) bitmapKindForCategory(category) = IntClass - else bitmapKindForCategory(category) = LongClass - } - clazz.info.decls.toList groupBy bitmapCategory foreach { - case (nme.NO_NAME, _) => () - case (category, fields) => fold(fields, category) + if (clazz.isTrait) + implementedAccessors filter { + case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false + case _ => true } - } - buildBitmapOffsets() - var stats1 = addCheckedGetters(clazz, stats) - - def getterBody(getter: Symbol) = { - assert(getter.isGetter) - val readValue = getter.tpe match { - // A field "final val f = const" in a trait generates a getter with a ConstantType. - case MethodType(Nil, ConstantType(c)) => - Literal(c) + else { + /* If `stat` is a superaccessor, complete it by adding a right-hand side. + * Note: superaccessors are always abstract until this point. + * The method to call in a superaccessor is stored in the accessor symbol's alias field. + * The rhs is: + * super.A(xs) where A is the super accessor's alias and xs are its formal parameters. + * This rhs is typed and then mixin transformed. + */ + def completeSuperAccessor(stat: Tree) = stat match { + case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor => + val body = atPos(stat.pos)(Apply(SuperSelect(clazz, stat.symbol.alias), vparams map (v => Ident(v.symbol)))) + val pt = stat.symbol.tpe.resultType + + copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt)))) case _ => - // if it is a mixed-in lazy value, complete the accessor - if (getter.isLazy) { - val isUnit = isUnitGetter(getter) - val initCall = Apply(staticRef(initializer(getter)), gen.mkAttributedThis(clazz) :: Nil) - val selection = fieldAccess(getter) - val init = if (isUnit) initCall else atPos(getter.pos)(Assign(selection, initCall)) - val returns = if (isUnit) UNIT else selection - mkLazyDef(clazz, getter, List(init), returns, fieldOffset(getter)) - } - // For a field of type Unit in a trait, no actual field is generated when being mixed in. - else if (isUnitGetter(getter)) UNIT - else fieldAccess(getter) + stat } - if (!needsInitFlag(getter)) readValue - else mkCheckedAccessor(clazz, readValue, fieldOffset(getter), getter.pos, getter) - } - def setterBody(setter: Symbol) = { - val getter = setter.getterIn(clazz) - - // A trait with a field of type Unit creates a trait setter (invoked by the - // implementation class constructor), like for any other trait field. - // However, no actual field is created in the class that mixes in the trait. - // Therefore the setter does nothing (except setting the -Xcheckinit flag). - - val setInitFlag = - if (!needsInitFlag(getter)) Nil - else List(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter))) - - val fieldInitializer = - if (isUnitGetter(getter)) Nil - else List(Assign(fieldAccess(setter), Ident(setter.firstParam))) - - (fieldInitializer ::: setInitFlag) match { - case Nil => UNIT - // If there's only one statement, the Block factory does not actually create a Block. - case stats => Block(stats: _*) - } + implementedAccessors map completeSuperAccessor } - - def isUnitGetter(getter: Symbol) = getter.tpe.resultType.typeSymbol == UnitClass - def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed) - - def isOverriddenSetter(sym: Symbol) = - nme.isTraitSetterName(sym.name) && { - val other = sym.nextOverriddenSymbol - isOverriddenAccessor(other.getterIn(other.owner), clazz.info.baseClasses) - } - - // for all symbols `sym` in the class definition, which are mixed in: - for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { - // if current class is a trait interface, add an abstract method for accessor `sym` - if (clazz hasFlag lateINTERFACE) { - addDefDef(sym) - } - // if class is not a trait add accessor definitions - else if (!clazz.isTrait) { - if (isConcreteAccessor(sym)) { - // add accessor definitions - addDefDef(sym, { - if (sym.isSetter) { - // If this is a setter of a mixed-in field which is overridden by another mixin, - // the trait setter of the overridden one does not need to do anything - the - // trait setter of the overriding field will initialize the field. - if (isOverriddenSetter(sym)) UNIT - else setterBody(sym) - } - else getterBody(sym) - }) - } - else if (sym.isModule && !(sym hasFlag LIFTED | BRIDGE)) { - // add modules - val vsym = sym.owner.newModuleVarSymbol(sym) - addDef(position(sym), ValDef(vsym)) - - // !!! TODO - unravel the enormous duplication between this code and - // eliminateModuleDefs in RefChecks. - val rhs = gen.newModule(sym, vsym.tpe) - val assignAndRet = gen.mkAssignAndReturn(vsym, rhs) - val attrThis = gen.mkAttributedThis(clazz) - val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List()) - - addDefDef(sym, rhs1) - } - else if (!sym.isMethod) { - // add fields - addValDef(sym) - } - else if (sym.isSuperAccessor) { - // add superaccessors - addDefDef(sym) - } - else { - // add forwarders - assert(sym.alias != NoSymbol, sym) - // debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString) - if (!sym.isMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident))) - } - } - } - stats1 = add(stats1, newDefs.toList) - if (!clazz.isTrait) stats1 = stats1 map completeSuperAccessor - stats1 - } - - private def nullableFields(templ: Template): Map[Symbol, Set[Symbol]] = { - val scope = templ.symbol.owner.info.decls - // if there are no lazy fields, take the fast path and save a traversal of the whole AST - if (scope exists (_.isLazy)) { - val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() - // check what fields can be nulled for - for ((field, users) <- singleUseFields(templ); lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr)) - map(lazyFld) += field - - map.toMap - } - else Map() } /** The transform that gets applied to a tree after it has been completely * traversed and possible modified by a preTransform. * This step will - * - change every node type that refers to an implementation class to its - * corresponding interface, unless the node's symbol is an implementation class. * - change parents of templates to conform to parents in the symbol info * - add all new definitions to a class or interface * - remove widening casts @@ -1142,105 +543,37 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * to static calls of methods in implementation modules (@see staticCall) * - change super calls to methods in implementation classes to static calls * (@see staticCall) - * - change `this` in implementation modules to references to the self parameter - * - refer to fields in some implementation class via an abstract method in the interface. */ private def postTransform(tree: Tree): Tree = { - def siteWithinImplClass = currentOwner.enclClass.isImplClass val sym = tree.symbol - // change every node type that refers to an implementation class to its - // corresponding interface, unless the node's symbol is an implementation class. - if (tree.tpe.typeSymbol.isImplClass && ((sym eq null) || !sym.isImplClass)) - tree modifyType toInterface - tree match { case templ @ Template(parents, self, body) => // change parents of templates to conform to parents in the symbol info val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos) - // mark fields which can be nulled afterward - lazyValNullables = nullableFields(templ) withDefaultValue Set() - // add all new definitions to current class or interface - treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, body)) - - // remove widening casts - case Apply(TypeApply(Select(qual, _), targ :: _), _) if isCastSymbol(sym) && (qual.tpe <:< targ.tpe) => - qual - - case Apply(Select(qual, _), args) => - /* Changes `qual.m(args)` where m refers to an implementation - * class method to Q.m(S, args) where Q is the implementation module of - * `m` and S is the self parameter for the call, which - * is determined as follows: - * - if qual != super, qual itself - * - if qual == super, and we are in an implementation class, - * the current self parameter. - * - if qual == super, and we are not in an implementation class, `this` - */ - def staticCall(target: Symbol) = { - def implSym = implClass(sym.owner).info.member(sym.name) - assert(target ne NoSymbol, - List(sym + ":", sym.tpe, sym.owner, implClass(sym.owner), implSym, - enteringPrevPhase(implSym.tpe), phase) mkString " " - ) - typedPos(tree.pos)(Apply(staticRef(target), transformSuper(qual) :: args)) - } - - if (isStaticOnly(sym)) { - // change calls to methods which are defined only in implementation - // classes to static calls of methods in implementation modules - staticCall(sym) - } - else qual match { - case Super(_, mix) => - // change super calls to methods in implementation classes to static calls. - // Transform references super.m(args) as follows: - // - if `m` refers to a trait, insert a static call to the corresponding static - // implementation - // - otherwise return tree unchanged - assert( - !(mix == tpnme.EMPTY && siteWithinImplClass), - "illegal super in trait: " + currentOwner.enclClass + " " + tree - ) - if (sym.owner hasFlag lateINTERFACE) { - if (sym.hasAccessorFlag) { - assert(args.isEmpty, args) - val sym1 = sym.overridingSymbol(currentOwner.enclClass) - typedPos(tree.pos)((transformSuper(qual) DOT sym1)()) - } - else { - staticCall(enteringPrevPhase(sym.overridingSymbol(implClass(sym.owner)))) - } - } - else { - assert(!siteWithinImplClass, currentOwner.enclClass) - tree - } + // add all new definitions to current class or interface + val statsWithNewDefs = addNewDefs(currentOwner, body) + statsWithNewDefs foreach { + case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) => + dd.symbol.updateAttachment(NeedStaticImpl) case _ => - tree } + treeCopy.Template(tree, parents1, self, statsWithNewDefs) - case This(_) => - transformThis(tree) - - case Select(Super(_, _), name) => - tree + case Select(qual, name) if sym.owner.isTrait && !sym.isMethod => + assert(sym.hasFlag(PARAMACCESSOR | PRESUPER), s"!!! Unexpected reference to field $sym in trait $currentOwner") - case Select(qual, name) if sym.owner.isImplClass && !isStaticOnly(sym) => - assert(!sym.isMethod, "no method allowed here: %s%s %s".format(sym, sym.isImplOnly, sym.flagString)) - // refer to fields in some implementation class via an abstract - // getter in the interface. - val iface = toInterface(sym.owner.tpe).typeSymbol - val ifaceGetter = sym getterIn iface + // refer to fields in some trait an abstract getter in the interface. + val ifaceGetter = sym getterIn sym.owner - if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + iface) + if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + sym.owner) else typedPos(tree.pos)((qual DOT ifaceGetter)()) case Assign(Apply(lhs @ Select(qual, _), List()), rhs) => - // assign to fields in some implementation class via an abstract - // setter in the interface. - def setter = lhs.symbol.setterIn(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos + // assign to fields in some trait via an abstract setter in the interface. + // Note that the case above has added the empty application. + val setter = lhs.symbol.setterIn(lhs.symbol.owner.tpe.typeSymbol) setPos lhs.pos typedPos(tree.pos)((qual DOT setter)(rhs)) @@ -1262,4 +595,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { finally localTyper = saved } } + + private def isTraitMethodRequiringStaticImpl(dd: DefDef): Boolean = { + val sym = dd.symbol + dd.rhs.nonEmpty && + sym.owner.isTrait && + !sym.isPrivate && // no need to put implementations of private methods into a static method + !sym.hasFlag(Flags.STATIC) + } + + case object NeedStaticImpl extends PlainAttachment } |