summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/transform/Mixin.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/Mixin.scala')
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala1305
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
}