diff options
Diffstat (limited to 'src')
34 files changed, 1246 insertions, 1460 deletions
diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala index 091d42bb6d..581ce8256a 100644 --- a/src/compiler/scala/reflect/reify/phases/Reshape.scala +++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala @@ -49,13 +49,13 @@ trait Reshape { if (discard) hk else ta case classDef @ ClassDef(mods, name, params, impl) => val Template(parents, self, body) = impl - var body1 = trimAccessors(classDef, reshapeLazyVals(body)) + var body1 = trimAccessors(classDef, body) body1 = trimSyntheticCaseClassMembers(classDef, body1) val impl1 = Template(parents, self, body1).copyAttrs(impl) ClassDef(mods, name, params, impl1).copyAttrs(classDef) case moduledef @ ModuleDef(mods, name, impl) => val Template(parents, self, body) = impl - var body1 = trimAccessors(moduledef, reshapeLazyVals(body)) + var body1 = trimAccessors(moduledef, body) body1 = trimSyntheticCaseClassMembers(moduledef, body1) val impl1 = Template(parents, self, body1).copyAttrs(impl) ModuleDef(mods, name, impl1).copyAttrs(moduledef) @@ -63,10 +63,10 @@ trait Reshape { val discardedParents = parents collect { case tt: TypeTree => tt } filter isDiscarded if (reifyDebug && discardedParents.length > 0) println("discarding parents in Template: " + discardedParents.mkString(", ")) val parents1 = parents diff discardedParents - val body1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(body)) + val body1 = trimSyntheticCaseClassCompanions(body) Template(parents1, self, body1).copyAttrs(template) case block @ Block(stats, expr) => - val stats1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(stats)) + val stats1 = trimSyntheticCaseClassCompanions(stats) Block(stats1, expr).copyAttrs(block) case unapply @ UnApply(Unapplied(Select(fun, nme.unapply | nme.unapplySeq)), args) => if (reifyDebug) println("unapplying unapply: " + tree) @@ -306,34 +306,6 @@ trait Reshape { stats1 } - private def reshapeLazyVals(stats: List[Tree]): List[Tree] = { - val lazyvaldefs:Map[Symbol, DefDef] = stats.collect({ case ddef: DefDef if ddef.mods.isLazy => ddef }). - map((ddef: DefDef) => ddef.symbol -> ddef).toMap - // lazy valdef and defdef are in the same block. - // only that valdef needs to have its rhs rebuilt from defdef - stats flatMap (stat => stat match { - case vdef: ValDef if vdef.symbol.isLazy => - if (reifyDebug) println(s"reconstructing original lazy value for $vdef") - val ddefSym = vdef.symbol.lazyAccessor - val vdef1 = lazyvaldefs.get(ddefSym) match { - case Some(ddef) => - toPreTyperLazyVal(ddef) - case None => - if (reifyDebug) println("couldn't find corresponding lazy val accessor") - vdef - } - if (reifyDebug) println(s"reconstructed lazy val is $vdef1") - vdef1::Nil - case ddef: DefDef if ddef.symbol.isLazy => - if (isUnitType(ddef.symbol.info)) { - // since lazy values of type Unit don't have val's - // we need to create them from scratch - toPreTyperLazyVal(ddef) :: Nil - } else Nil - case _ => stat::Nil - }) - } - private def trimSyntheticCaseClassMembers(deff: Tree, stats: List[Tree]): List[Tree] = stats filterNot (memberDef => memberDef.isDef && { val isSynthetic = memberDef.symbol.isSynthetic diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index e9d1dfe4d2..511572f6f3 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -88,7 +88,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { object CompilerPhase extends PermissibleValue { val values = List("namer", "typer", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", - "erasure", "lazyvals", "lambdalift", "constructors", + "erasure", "fields", "lambdalift", "constructors", "flatten", "mixin", "delambdafy", "cleanup", "jvm", "terminal") } diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index af866e1a6f..32c446e16a 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -516,17 +516,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = Some("erasure") } with PostErasure - // phaseName = "lazyvals" - object lazyVals extends { - val global: Global.this.type = Global.this - val runsAfter = List("erasure") - val runsRightAfter = None - } with LazyVals // phaseName = "lambdalift" object lambdaLift extends { val global: Global.this.type = Global.this - val runsAfter = List("lazyvals") + val runsAfter = List("erasure") val runsRightAfter = None } with LambdaLift @@ -620,13 +614,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter) pickler -> "serialize symbol tables", refChecks -> "reference/override checking, translate nested objects", uncurry -> "uncurry, translate function values to anonymous classes", - fields -> "synthesize accessors and fields", + fields -> "synthesize accessors and fields, including bitmaps for lazy vals", tailCalls -> "replace tail calls by jumps", specializeTypes -> "@specialized-driven class and method specialization", explicitOuter -> "this refs to outer pointers", erasure -> "erase types, add interfaces for traits", postErasure -> "clean up erased inline classes", - lazyVals -> "allocate bitmaps, translate lazy vals into lazified defs", lambdaLift -> "move nested functions to top level", constructors -> "move field definitions into constructors", mixer -> "mixin composition", @@ -1258,7 +1251,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val explicitouterPhase = phaseNamed("explicitouter") val erasurePhase = phaseNamed("erasure") val posterasurePhase = phaseNamed("posterasure") - // val lazyvalsPhase = phaseNamed("lazyvals") val lambdaliftPhase = phaseNamed("lambdalift") // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 5dddf30c96..762456c9c9 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -91,7 +91,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { ) /** Make a synchronized block on 'monitor'. */ - def mkSynchronized(monitor: Tree, body: Tree): Tree = + def mkSynchronized(monitor: Tree)(body: Tree): Tree = Apply(Select(monitor, Object_synchronized), List(body)) def mkAppliedTypeForCase(clazz: Symbol): Tree = { @@ -233,26 +233,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { else Block(prefix, containing) setPos (prefix.head.pos union containing.pos) } - /** Return the synchronized part of the double-checked locking idiom around the syncBody tree. It guards with `cond` and - * synchronizes on `clazz.this`. Additional statements can be included after initialization, - * (outside the synchronized block). - * - * The idiom works only if the condition is using a volatile field. - * - * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html - */ - def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = - mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats) - - def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = { - def blockOrStat(stats: List[Tree]): Tree = stats match { - case head :: Nil => head - case _ => Block(stats : _*) - } - val sync = mkSynchronized(attrThis, If(cond, blockOrStat(syncBody), EmptyTree)) - blockOrStat(sync :: stats) - } - /** Creates a tree representing new Object { stats }. * To make sure an anonymous subclass of Object is created, * if there are no stats, a () is added. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 630b2b6c7f..1982c7f643 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -5,11 +5,15 @@ package scala.tools.nsc.backend.jvm -import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} -import java.io.{StringWriter, PrintWriter} -import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} -import scala.tools.asm.{ClassReader, ClassWriter, Attribute} +import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, FieldNode, InsnList, MethodNode} +import java.io.{PrintWriter, StringWriter} +import java.util + +import scala.tools.asm.util.{CheckClassAdapter, Textifier, TraceClassVisitor, TraceMethodVisitor} +import scala.tools.asm.{Attribute, ClassReader, ClassWriter} import scala.collection.JavaConverters._ +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} import scala.tools.nsc.backend.jvm.analysis.InitialProducer import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype @@ -64,21 +68,37 @@ object AsmUtils { bytes } - def textifyClassStably(bytes: Array[Byte]): Unit = { + def classFromBytes(bytes: Array[Byte]): ClassNode = { val node = new ClassNode() new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) - node.fields = node.fields.asScala.sortBy(_.name).asJava - node.methods = node.methods.asScala.sortBy(_.name).asJava - node.visibleAnnotations = null - node.attrs = null - node.invisibleAnnotations = null + node + } + +// def main(args: Array[String]): Unit = println(textify(sortedClassRead(classBytes(args.head)))) + + def sortClassMembers(node: ClassNode): node.type = { + node.fields.sort(_.name compareTo _.name) + node.methods.sort(_.name compareTo _.name) + node + } + + // drop ScalaSig annotation and class attributes + def zapScalaClassAttrs(node: ClassNode): node.type = { + if (node.visibleAnnotations != null) + node.visibleAnnotations = node.visibleAnnotations.asScala.filterNot(a => a == null || a.desc.contains("Lscala/reflect/ScalaSignature")).asJava - println(textify(node)) + node.attrs = null + node } - def main(args: Array[String]): Unit = { - textifyClassStably(classBytes(args.head)) + def main(args: Array[String]): Unit = args.par.foreach { classFileName => + val node = zapScalaClassAttrs(sortClassMembers(classFromBytes(classBytes(classFileName)))) + + val pw = new PrintWriter(classFileName + ".asm") + val trace = new TraceClassVisitor(pw) + node.accept(trace) + pw.close() } /** diff --git a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala new file mode 100644 index 0000000000..120ee5c26e --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala @@ -0,0 +1,465 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL and Lightbend, Inc + */ + +package scala.tools.nsc +package transform + +import symtab._ +import Flags._ +import scala.collection.mutable + +trait AccessorSynthesis extends Transform with ast.TreeDSL { + import global._ + import definitions._ + import CODE._ + + val EmptyThicket = EmptyTree + def Thicket(trees: List[Tree]) = if (trees.isEmpty) EmptyTree else Block(trees, EmptyTree) + def mustExplodeThicket(tree: Tree): Boolean = + tree match { + case EmptyTree => true + case Block(_, EmptyTree) => true + case _ => false + } + def explodeThicket(tree: Tree): List[Tree] = tree match { + case EmptyTree => Nil + case Block(thicket, EmptyTree) => thicket + case stat => stat :: Nil + } + + + trait AccessorTreeSynthesis { + protected def typedPos(pos: Position)(tree: Tree): Tree + + // used while we still need to synthesize some accessors in mixins: paramaccessors and presupers + class UncheckedAccessorSynth(protected val clazz: Symbol){ + protected val _newDefs = mutable.ListBuffer[Tree]() + + def newDefs = _newDefs.toList + + /** Add tree at given position as new definition */ + protected def addDef(tree: ValOrDefDef): Unit = _newDefs += typedPos(position(tree.symbol))(tree) + + /** The position of given symbol, or, if this is undefined, + * the position of the current class. + */ + private def position(sym: Symbol) = if (sym.pos == NoPosition) clazz.pos else sym.pos + + /** Add new method definition. + * + * @param sym The method symbol. + * @param rhs The method body. + */ + def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(DefDef(sym, rhs)) + def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(ValDef(sym, rhs)) + + /** Complete `stats` with init checks and bitmaps, + * removing any abstract method definitions in `stats` that are + * matched by some symbol defined by a tree previously passed to `addDef`. + */ + def implementWithNewDefs(stats: List[Tree]): List[Tree] = { + val newDefs = _newDefs.toList + 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) + } + + def accessorBody(sym: Symbol) = + if (sym.isSetter) setterBody(sym, sym.getterIn(clazz)) else getterBody(sym) + + protected def getterBody(getter: Symbol): Tree = { + assert(getter.isGetter) + assert(getter.hasFlag(PARAMACCESSOR)) + + fieldAccess(getter) + } + + protected def setterBody(setter: Symbol, getter: Symbol): Tree = { + assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz") + + Assign(fieldAccess(setter), Ident(setter.firstParam)) + } + + private def fieldAccess(accessor: Symbol) = + Select(This(clazz), accessor.accessed) + + } + } + + case class BitmapInfo(symbol: Symbol, mask: Literal) { + def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass + } + + + // TODO: better way to communicate from info transform to tree transfor? + private[this] val _bitmapInfo = perRunCaches.newMap[Symbol, BitmapInfo] + private[this] val _slowPathFor = perRunCaches.newMap[Symbol, Symbol]() + + def checkedAccessorSymbolSynth(clz: Symbol) = + if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz } + else new CheckedAccessorSymbolSynth { val clazz = clz } + + // base trait, with enough functionality for lazy vals -- CheckInitAccessorSymbolSynth adds logic for -Xcheckinit + trait CheckedAccessorSymbolSynth { + protected val clazz: Symbol + + protected def defaultPos = clazz.pos.focus + protected def isTrait = clazz.isTrait + protected def hasTransientAnnot(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr + + def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized + + + /** 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. + * + * bitmaps for checkinit fields are not inherited + */ + protected def bitmapCategory(sym: Symbol): Name = { + // ensure that nested objects are transformed TODO: still needed? + sym.initialize + + import nme._ + + if (needsBitmap(sym) && sym.isLazy) + if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL + else NO_NAME + } + + + def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym) + protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym + + + /** Fill the map from fields to bitmap infos. + * + * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later. + */ + def computeBitmapInfos(decls: List[Symbol]): List[Symbol] = { + def doCategory(fields: List[Symbol], category: Name) = { + val nbFields = fields.length // we know it's > 0 + val (bitmapClass, bitmapCapacity) = + if (nbFields == 1) (BooleanClass, 1) + else if (nbFields <= 8) (ByteClass, 8) + else if (nbFields <= 32) (IntClass, 32) + else (LongClass, 64) + + // 0-based index of highest bit, divided by bits per bitmap + // note that this is only ever > 0 when bitmapClass == LongClass + val maxBitmapNumber = (nbFields - 1) / bitmapCapacity + + // transient fields get their own category + val isTransientCategory = fields.head hasAnnotation TransientAttr + + val bitmapSyms = + (0 to maxBitmapNumber).toArray map { bitmapNumber => + val bitmapSym = ( + clazz.newVariable(nme.newBitmapName(category, bitmapNumber).toTermName, defaultPos) + setInfo bitmapClass.tpe + setFlag PrivateLocal | NEEDS_TREES + ) + + bitmapSym addAnnotation VolatileAttr + + if (isTransientCategory) bitmapSym addAnnotation TransientAttr + + bitmapSym + } + + fields.zipWithIndex foreach { case (f, idx) => + val bitmapIdx = idx / bitmapCapacity + val offsetInBitmap = idx % bitmapCapacity + val mask = + if (bitmapClass == LongClass) Constant(1L << offsetInBitmap) + else Constant(1 << offsetInBitmap) + + _bitmapInfo(f) = BitmapInfo(bitmapSyms(bitmapIdx), Literal(mask)) + } + + bitmapSyms + } + + decls groupBy bitmapCategory flatMap { + case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category) + case _ => Nil + } toList + } + + def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal) + + def newSlowPathSymbol(lzyVal: Symbol): Symbol = { + val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else defaultPos // TODO: is the else branch ever taken? + val sym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfo MethodType(Nil, lzyVal.tpe.resultType) + _slowPathFor(lzyVal) = sym + sym + } + + } + + trait CheckInitAccessorSymbolSynth extends CheckedAccessorSymbolSynth { + /** 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. + */ + protected def needsInitFlag(sym: Symbol): Boolean = + 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)) + + /** 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. + * + * bitmaps for checkinit fields are not inherited + */ + override protected def bitmapCategory(sym: Symbol): Name = { + import nme._ + + super.bitmapCategory(sym) match { + case NO_NAME if needsInitFlag(sym) && !sym.isDeferred => + if (hasTransientAnnot(sym)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + case category => category + } + } + + override def needsBitmap(sym: Symbol): Boolean = super.needsBitmap(sym) || !(isTrait || sym.isDeferred) && needsInitFlag(sym) + } + + + // synthesize trees based on info gathered during info transform + // (which are known to have been run because the tree transform runs afterOwnPhase) + // since we can't easily share all info via symbols and flags, we have two maps above + // (they are persisted even between phases because the -Xcheckinit logic runs during constructors) + // TODO: can we use attachments instead of _bitmapInfo and _slowPathFor? + trait CheckedAccessorTreeSynthesis extends AccessorTreeSynthesis { + + // note: we deal in getters here, not field symbols + trait SynthCheckedAccessorsTreesInClass extends CheckedAccessorSymbolSynth { + def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + def thisRef = gen.mkAttributedThis(clazz) + + /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the + * precise comparison operator depending on the value of 'equalToZero'. + */ + def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = { + val bitmap = bitmapFor(field) + val bitmapTree = thisRef DOT bitmap.symbol + + if (bitmap.storageClass == BooleanClass) { + if (equalToZero) NOT(bitmapTree) else bitmapTree + } else { + val lhs = bitmapTree GEN_&(bitmap.mask, bitmap.storageClass) + if (equalToZero) lhs GEN_==(ZERO, bitmap.storageClass) + else lhs GEN_!=(ZERO, bitmap.storageClass) + } + } + + /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ + def mkSetFlag(valSym: Symbol): Tree = { + val bitmap = bitmapFor(valSym) + def x = thisRef DOT bitmap.symbol + + Assign(x, + if (bitmap.storageClass == BooleanClass) TRUE + else { + val or = Apply(Select(x, getMember(bitmap.storageClass, nme.OR)), List(bitmap.mask)) + // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?) + // TODO: need this to make it type check -- is there another way?? + if (bitmap.storageClass != LongClass) Apply(Select(or, newTermName("to" + bitmap.storageClass.name)), Nil) + else or + } + ) + } + } + + class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass { + /** + * The compute method (slow path) looks like: + * + * ``` + * def l$compute() = { + * synchronized(this) { + * if ((bitmap$n & MASK) == 0) { + * init // l$ = <rhs> + * bitmap$n = bimap$n | MASK + * } + * } + * ... + * this.f1 = null + * ... + * this.fn = null + * l$ + * } + * ``` + * + * `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. + * + * Private fields used only in this initializer are subsequently set to null. + * + * 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. + */ + def expandLazyClassMember(lazyVar: Symbol, lazyAccessor: Symbol, transformedRhs: Tree, nullables: Map[Symbol, List[Symbol]]): Tree = { + // use cast so that specialization can turn null.asInstanceOf[T] into null.asInstanceOf[Long] + def nullify(sym: Symbol) = + Select(thisRef, sym.accessedOrSelf) === gen.mkAsInstanceOf(NULL, sym.info.resultType) + + val nulls = nullables.getOrElse(lazyAccessor, Nil) map nullify + + if (nulls.nonEmpty) + log("nulling fields inside " + lazyAccessor + ": " + nulls) + + val slowPathSym = slowPathFor(lazyAccessor) + val rhsAtSlowDef = transformedRhs.changeOwner(lazyAccessor -> slowPathSym) + + val isUnit = isUnitGetter(lazyAccessor) + val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) + val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) + + def needsInit = mkTest(lazyAccessor) + val doInit = Block(List(storeRes), mkSetFlag(lazyAccessor)) + // the slow part of double-checked locking (TODO: is this the most efficient pattern? https://github.come/scala/scala-dev/issues/204) + val slowPathRhs = Block(gen.mkSynchronized(thisRef)(If(needsInit, doInit, EmptyTree)) :: nulls, selectVar) + + // The lazy accessor delegates to the compute method if needed, otherwise just accesses the var (it was initialized previously) + // `if ((bitmap&n & MASK) == 0) this.l$compute() else l$` + val accessorRhs = If(needsInit, Apply(Select(thisRef, slowPathSym), Nil), selectVar) + + afterOwnPhase { // so that we can assign to vals + Thicket(List((DefDef(slowPathSym, slowPathRhs)), DefDef(lazyAccessor, accessorRhs)) map typedPos(lazyAccessor.pos.focus)) + } + } + } + + /** Map lazy values to the fields they should null after initialization. */ + // TODO: fix + 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 // 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) + } + } + } + templStats foreach SingleUseTraverser.apply + // println("usedIn: " + usedIn) + + // only consider usages from non-transient lazy vals (SI-9365) + val singlyUsedIn = usedIn filter { case (_, member :: Nil) => member.isLazy && !member.accessed.hasAnnotation(TransientAttr) case _ => false } toMap + + // println("singlyUsedIn: " + singlyUsedIn) + singlyUsedIn + } + + 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 + + map.mapValues(_.toList sortBy (_.id)).toMap + } + } + + + class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth { + private object addInitBitsTransformer extends Transformer { + private def checkedGetter(lhs: Tree)(pos: Position) = { + val getter = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) + if (hasBitmap(getter) && needsInitFlag(getter)) { + debuglog("adding checked getter for: " + getter + " " + lhs.symbol.flagString) + List(typedPos(pos)(mkSetFlag(getter))) + } + else Nil + } + 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)(stat.pos.focus) + // remove initialization for default values -- TODO is this case ever hit? constructors does not generate Assigns with EmptyTree for the rhs AFAICT + case Apply(lhs@Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil + case stat => List(stat) + }, + exprOwner + ) + } + } + + /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */ + def wrapRhsWithInitChecks(sym: Symbol)(rhs: Tree): Tree = { + // Add statements to the body of a constructor to set the 'init' bit for each field initialized in the constructor + if (sym.isConstructor) addInitBitsTransformer transform rhs + else if (isTrait || rhs == EmptyTree) rhs + else if (needsInitFlag(sym)) // getter + mkCheckedAccessorRhs(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym) + else if (sym.isSetter) { + val getter = sym.getterIn(clazz) + if (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) + else rhs + } + else rhs + } + + private def mkCheckedAccessorRhs(retVal: Tree, pos: Position, getter: Symbol): Tree = { + val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" + val result = + IF(mkTest(getter, equalToZero = false)). + THEN(retVal). + ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) + + typedPos(pos)(BLOCK(result, retVal)) + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index 0fb6213d36..81df28bc87 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -456,6 +456,11 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { super.transform(treeCopy.ApplyDynamic(tree, atPos(fn.pos)(Ident(SymbolLiteral_dummy).setType(SymbolLiteral_dummy.info)), LIT(SymbolLiteral_bootstrap) :: arg :: Nil)) + // Drop the TypeApply, which was used in Erasure to make `synchronized { ... } ` erase like `...` + // (and to avoid boxing the argument to the polymorphic `synchronized` method). + case app@Apply(TypeApply(fun, _), args) if fun.symbol == Object_synchronized => + super.transform(treeCopy.Apply(app, fun, args)) + // Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), <tag>)` // with just `ArrayValue(...).$asInstanceOf[...]` // diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 0a87e358b4..8d362f13dd 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -450,7 +450,9 @@ abstract class Constructors extends Statics with Transform with TypingTransforme with DelayedInitHelper with OmittablesHelper with GuardianOfCtorStmts - { + with fields.CheckedAccessorTreeSynthesis + { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) val clazz = impl.symbol.owner // the transformed class @@ -770,11 +772,20 @@ abstract class Constructors extends Statics with Transform with TypingTransforme // We never eliminate delayed hooks or the constructors, so, only filter `defs`. val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors + val statsWithInitChecks = + if (settings.checkInit) { + val addChecks = new SynthInitCheckedAccessorsIn(currentOwner) + prunedStats mapConserve { + case dd: DefDef => deriveDefDef(dd)(addChecks.wrapRhsWithInitChecks(dd.symbol)) + case stat => stat + } + } else prunedStats + // Add the static initializers - if (classInitStats.isEmpty) deriveTemplate(impl)(_ => prunedStats) + if (classInitStats.isEmpty) deriveTemplate(impl)(_ => statsWithInitChecks) else { - val staticCtor = staticConstructor(prunedStats, localTyper, impl.pos)(classInitStats) - deriveTemplate(impl)(_ => staticCtor :: prunedStats) + val staticCtor = staticConstructor(statsWithInitChecks, localTyper, impl.pos)(classInitStats) + deriveTemplate(impl)(_ => staticCtor :: statsWithInitChecks) } } } // TemplateTransformer diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index eecd52546c..69240b07a1 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1117,7 +1117,8 @@ abstract class Erasure extends InfoTransform case TypeApply(fun, args) if (fun.symbol.owner != AnyClass && fun.symbol != Object_asInstanceOf && - fun.symbol != Object_isInstanceOf) => + fun.symbol != Object_isInstanceOf && + fun.symbol != Object_synchronized) => // leave all other type tests/type casts, remove all other type applications preErase(fun) @@ -1194,7 +1195,7 @@ abstract class Erasure extends InfoTransform else { val tree1 = preErase(tree) tree1 match { - case TypeApply(fun, targs @ List(targ)) if fun.symbol == Any_asInstanceOf && targ.tpe == UnitTpe => + case TypeApply(fun, targs @ List(targ)) if (fun.symbol == Any_asInstanceOf || fun.symbol == Object_synchronized) && targ.tpe == UnitTpe => // SI-9066 prevent transforming `o.asInstanceOf[Unit]` to `o.asInstanceOf[BoxedUnit]`. // adaptMember will then replace the call by a reference to BoxedUnit.UNIT. treeCopy.TypeApply(tree1, transform(fun), targs).clearType() diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 26e517743a..3529fbd0eb 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -10,26 +10,38 @@ import scala.annotation.tailrec import symtab.Flags._ -/** Synthesize accessors and field for each (strict) val owned by a trait. +/** Synthesize accessors, fields (and bitmaps) for (lazy) vals and modules. * - * For traits: + * During Namers, a `ValDef` that is `lazy`, deferred and/or defined in a trait carries its getter's symbol. + * The underlying field symbol does not exist until this phase. * - * - Namers translates a definition `val x = rhs` into a getter `def x = rhs` -- no underlying field is created. - * - This phase synthesizes accessors and fields for any vals mixed into a non-trait class. - * - Constructors will move the rhs to an assignment in the template body. - * Those statements then move to the template into the constructor, - * which means it will initialize the fields defined in this template (and execute the corresponding side effects). - * We need to maintain the connection between getter and rhs until after specialization so that it can duplicate vals. - * - A ModuleDef is desugared to a ClassDef, an accessor (which reuses the module's term symbol) - * and a module var (unless the module is static and does not implement a member of a supertype, or we're in a trait). - * For subclasses of traits that define modules, a module var is mixed in, as well as the required module accessors. + * For `val`s defined in classes, we still emit a field immediately. + * TODO: uniformly assign getter symbol to all `ValDef`s, stop using `accessed`. * - * Runs after uncurry to deal with classes that implement SAM traits with ValDefs. - * Runs before erasure (to get bridges), and thus before lambdalift/flatten, so that nested functions/definitions must be considered. + * This phase synthesizes accessors, fields and bitmaps (for lazy or init-checked vals under -Xcheckinit) + * in the first (closest in the subclassing lattice) subclass (not a trait) of a trait. * - * We run after uncurry because it can introduce subclasses of traits with fields (SAMs with vals). - * Lambdalift also introduces new fields (paramaccessors for captured vals), but runs too late in the pipeline - * (mixins still synthesizes implementations for accessors that need to be mixed into subclasses of local traits that capture). + * For lazy vals and modules, we emit accessors that using double-checked locking (DCL) to balance thread safety + * and performance. A lazy val gets a compute method for the DCL's slow path, for a module it's all done in the accessor. + * + * Local lazy vals do not receive bitmaps, but use a Lazy*Holder that has the volatile init bit and the computed value. + * See `mkLazyLocalDef`. + * + * Constructors will move the rhs to an assignment in the template body. + * Those statements then move to the template into the constructor, + * which means it will initialize the fields defined in this template (and execute the corresponding side effects). + * We need to maintain the connection between getter and rhs until after specialization so that it can duplicate vals. + * + * A ModuleDef is desugared to a ClassDef, an accessor (which reuses the module's term symbol) + * and a module var (unless the module is static and does not implement a member of a supertype, or we're in a trait). + * + * For subclasses of traits that define modules, a module var is mixed in, as well as the required module accessors. + * + * Phase ordering: + * - Runs after uncurry to deal with classes that implement SAM traits with ValDefs. + * - Runs before erasure (to get bridges), and thus before lambdalift/flatten, so that nested functions/definitions must be considered. + * - Lambdalift introduces new paramaccessors for captured vals, but runs too late in the pipeline, so + * mixins still synthesizes implementations for these accessors when a local trait that captures is subclassed. * * * In the future, would like to get closer to dotty, which lifts a val's RHS (a similar thing is done for template-level statements) @@ -54,10 +66,12 @@ import symtab.Flags._ * The only change due to overriding is that its value is never written to the field * (the overridden val's value is, of course, stored in the field in addition to its side-effect being performed). * - * TODO: check init support (or drop the -Xcheck-init flag??) + * TODO: Java 9 support for vals defined in traits. They are currently emitted as final, + * but the write (putfield) to the val does not occur syntactically within the <init> method + * (it's done by the trait setter, which is called from the trait's mixin constructor, + * which is called from the subclass's constructor...) */ -abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers { - +abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers with AccessorSynthesis { import global._ import definitions._ @@ -69,8 +83,8 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (sym.isJavaDefined || sym.isPackageClass || !sym.isClass) tp else synthFieldsAndAccessors(tp) - // we leave lazy vars/accessors and early-init vals alone for now - private def excludedAccessorOrFieldByFlags(statSym: Symbol): Boolean = statSym hasFlag LAZY | PRESUPER + // TODO: drop PRESUPER support when we implement trait parameters in 2.13 + private def excludedAccessorOrFieldByFlags(statSym: Symbol): Boolean = statSym hasFlag PRESUPER // used for internal communication between info and tree transform of this phase -- not pickled, not in initialflags // TODO: reuse MIXEDIN for NEEDS_TREES? @@ -139,13 +153,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor class FieldMemoization(accessorOrField: Symbol, site: Symbol) { val tp = fieldTypeOfAccessorIn(accessorOrField, site.thisType) - // not stored, no side-effect - val pureConstant = tp.isInstanceOf[ConstantType] - - // if !stored, may still have a side-effect - // (currently not distinguished -- used to think we could drop unit-typed vals, - // but the memory model cares about writes to unit-typed fields) - val stored = !pureConstant // || isUnitType(tp)) + // We can only omit strict vals of ConstantType. Lazy vals do not receive constant types (anymore). + // (See note at widenIfNecessary -- for example, the REPL breaks when we omit constant lazy vals) + // Note that a strict unit-typed val does receive a field, because we cannot omit the write to the field + // (well, we could emit it for non-@volatile ones, if I understand the memory model correctly, + // but that seems pretty edge-casey) + val constantTyped = tp.isInstanceOf[ConstantType] } private def fieldTypeForGetterIn(getter: Symbol, pre: Type): Type = getter.info.finalResultType.asSeenFrom(pre, getter.owner) @@ -174,22 +187,71 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // can't use the referenced field since it already tracks the module's moduleClass - private[this] val moduleVarOf = perRunCaches.newMap[Symbol, Symbol] + private[this] val moduleOrLazyVarOf = perRunCaches.newMap[Symbol, Symbol] - private def newModuleVarSymbol(site: Symbol, module: Symbol, tp: Type, extraFlags: Long): TermSymbol = { + // TODO: can we drop FINAL? In any case, since these variables are MUTABLE, they cannot and will + // not be emitted as ACC_FINAL. They are FINAL in the Scala sense, though: cannot be overridden. + private final val ModuleOrLazyFieldFlags = FINAL | PrivateLocal | SYNTHETIC | NEEDS_TREES + + private def newModuleVarSymbol(owner: Symbol, module: Symbol, tp: Type): TermSymbol = { // println(s"new module var in $site for $module of type $tp") - val moduleVar = site.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | extraFlags) setInfo tp addAnnotation VolatileAttr - moduleVarOf(module) = moduleVar + val flags = MODULEVAR | (if (owner.isClass) ModuleOrLazyFieldFlags else 0) + + val moduleVar = + (owner.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, flags) + setInfo tp + addAnnotation VolatileAttr) + + moduleOrLazyVarOf(module) = moduleVar moduleVar } private def moduleInit(module: Symbol) = { // println(s"moduleInit for $module in ${module.ownerChain} --> ${moduleVarOf.get(module)}") - val moduleVar = moduleVarOf(module) - gen.mkAssignAndReturn(moduleVar, gen.newModule(module, moduleVar.info)) + val moduleVar = moduleOrLazyVarOf(module) + def moduleVarRef = gen.mkAttributedRef(moduleVar) + + // for local modules, we synchronize on the owner of the method that owns the module + val monitorHolder = This(moduleVar.owner.enclClass) + def needsInit = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL)) + val init = Assign(moduleVarRef, gen.newModule(module, moduleVar.info)) + + /** double-checked locking following https://shipilev.net/blog/2014/safe-public-construction/#_safe_publication + * + * public class SafeDCLFactory { + * private volatile Singleton instance; + * + * public Singleton get() { + * if (instance == null) { // check 1 + * synchronized(this) { + * if (instance == null) { // check 2 + * instance = new Singleton(); + * } + * } + * } + * return instance; + * } + * } + * + * TODO: optimize using local variable? + */ + Block(If(needsInit, gen.mkSynchronized(monitorHolder)(If(needsInit, init, EmptyTree)), EmptyTree) :: Nil, moduleVarRef) } + // NoSymbol for lazy accessor sym with unit result type + def lazyVarOf(sym: Symbol) = moduleOrLazyVarOf.getOrElse(sym, NoSymbol) + + private def newLazyVarMember(clazz: Symbol, member: Symbol, tp: Type): TermSymbol = { + val flags = LAZY | (member.flags & FieldFlags) | ModuleOrLazyFieldFlags + val name = member.name.toTermName.append(reflect.NameTransformer.LOCAL_SUFFIX_STRING) + + // Set the MUTABLE flag because the field cannot be ACC_FINAL since we write to it outside of a constructor. + val sym = clazz.newVariable(name, member.pos.focus, flags) setInfo tp + + moduleOrLazyVarOf(member) = sym + sym + } private object synthFieldsAndAccessors extends TypeMap { private def newTraitSetter(getter: Symbol, clazz: Symbol) = { @@ -210,7 +272,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor private def newModuleAccessor(module: Symbol, site: Symbol, moduleVar: Symbol) = { val accessor = site.newMethod(module.name.toTermName, site.pos, STABLE | MODULE | NEEDS_TREES) - moduleVarOf(accessor) = moduleVar + moduleOrLazyVarOf(accessor) = moduleVar // we're in the same prefix as module, so no need for site.thisType.memberType(module) accessor setInfo MethodType(Nil, moduleVar.info) @@ -221,7 +283,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor accessor } - // needed for the following scenario (T could be trait or class) // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O. // marking it as an ACCESSOR so that it will get to `getterBody` when synthesizing trees below @@ -233,6 +294,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } + private def newSuperLazy(lazyCallingSuper: Symbol, site: Type, lazyVar: Symbol) = { + lazyCallingSuper.asTerm.referenced = lazyVar + + val tp = site.memberInfo(lazyCallingSuper) + + lazyVar setInfo tp.resultType + lazyCallingSuper setInfo tp + } + def apply(tp0: Type): Type = tp0 match { // TODO: make less destructive (name changes, decl additions, flag setting -- // none of this is actually undone when travelling back in time using atPhase) @@ -246,11 +316,16 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (member hasFlag ACCESSOR) { val fieldMemoization = fieldMemoizationIn(member, clazz) // check flags before calling makeNotPrivate - val accessorUnderConsideration = !(member hasFlag (DEFERRED | LAZY)) + val accessorUnderConsideration = !(member hasFlag DEFERRED) // destructively mangle accessor's name (which may cause rehashing of decls), also sets flags // this accessor has to be implemented in a subclass -- can't be private - if ((member hasFlag PRIVATE) && fieldMemoization.stored) member makeNotPrivate clazz + if ((member hasFlag PRIVATE) && !fieldMemoization.constantTyped) member makeNotPrivate clazz + // Since we need to refer to `member` using a super call in a subclass, we must ensure that access is allowed. + // If `member` has an access boundary, make sure the `PROTECTED` flag is set, + // to widen from `private[foo]` to `protected[foo]` + // (note that `member.hasAccessBoundary` implies `!member.hasFlag(PRIVATE)`, so we don't have to `resetFlag PRIVATE`) + else if (member.isLazy && member.hasAccessBoundary) member setFlag PROTECTED // This must remain in synch with publicizeTraitMethod in Mixins, so that the // synthesized member in a subclass and the trait member remain in synch regarding access. @@ -262,10 +337,10 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // (not sure why this only problem only arose when we started setting the notPROTECTED flag) // derive trait setter after calling makeNotPrivate (so that names are mangled consistently) - if (accessorUnderConsideration && fieldMemoization.stored) { + if (accessorUnderConsideration && !fieldMemoization.constantTyped) { synthesizeImplInSubclasses(member) - if (member hasFlag STABLE) // TODO: check isGetter? + if ((member hasFlag STABLE) && !(member hasFlag LAZY)) newDecls += newTraitSetter(member, clazz) } } else if (member hasFlag MODULE) { @@ -296,29 +371,37 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor (existingGetter ne NoSymbol) && (tp matches (site memberInfo existingGetter).resultType) // !existingGetter.isDeferred && -- see (3) } - def newModuleVar(member: Symbol): TermSymbol = - newModuleVarSymbol(clazz, member, site.memberType(member).resultType, PrivateLocal | SYNTHETIC | NEEDS_TREES) + def newModuleVarMember(member: Symbol): TermSymbol = + newModuleVarSymbol(clazz, member, site.memberType(member).resultType) + + def newLazyVarMember(member: Symbol): TermSymbol = + Fields.this.newLazyVarMember(clazz, member, site.memberType(member).resultType) // a module does not need treatment here if it's static, unless it has a matching member in a superclass // a non-static method needs a module var - val modulesNeedingExpansion = - oldDecls.toList.filter(m => m.isModule && (!m.isStatic || m.isOverridingSymbol)) + val modulesAndLazyValsNeedingExpansion = + oldDecls.toList.filter(m => (m.isModule && (!m.isStatic || m.isOverridingSymbol)) || m.isLazy) + + val accessorSymbolSynth = checkedAccessorSymbolSynth(tp.typeSymbol) // expand module def in class/object (if they need it -- see modulesNeedingExpansion above) - val expandedModules = - modulesNeedingExpansion map { module => + val expandedModulesAndLazyVals = ( + modulesAndLazyValsNeedingExpansion flatMap { member => + if (member.isLazy) { + List(newLazyVarMember(member), accessorSymbolSynth.newSlowPathSymbol(member)) + } // expanding module def (top-level or nested in static module) - if (module.isStatic) { // implies m.isOverridingSymbol as per above filter + else List(if (member.isStatic) { // implies m.isOverridingSymbol as per above filter // Need a module accessor, to implement/override a matching member in a superclass. // Never a need for a module var if the module is static. - newMatchingModuleAccessor(clazz, module) + newMatchingModuleAccessor(clazz, member) } else { - nonStaticModuleToMethod(module) + nonStaticModuleToMethod(member) // must reuse symbol instead of creating an accessor - module setFlag NEEDS_TREES - newModuleVar(module) - } - } + member setFlag NEEDS_TREES + newModuleVarMember(member) + }) + }) // println(s"expanded modules for $clazz: $expandedModules") @@ -344,16 +427,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor clonedAccessor } - if (member hasFlag MODULE) { - val moduleVar = newModuleVar(member) - List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) - } // when considering whether to mix in the trait setter, forget about conflicts -- they are reported for the getter // a trait setter for an overridden val will receive a unit body in the tree transform - else if (nme.isTraitSetterName(member.name)) { + if (nme.isTraitSetterName(member.name)) { val getter = member.getterIn(member.owner) val clone = cloneAccessor() - + setClonedTraitSetterFlags(clazz, getter, clone) // println(s"mixed in trait setter ${clone.defString}") @@ -362,7 +441,17 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // don't cause conflicts, skip overridden accessors contributed by supertraits (only act on the last overriding one) // see pos/trait_fields_dependent_conflict.scala and neg/t1960.scala else if (accessorConflictsExistingVal(member) || isOverriddenAccessor(member, clazz)) Nil - else if (member.isGetter && fieldMemoizationIn(member, clazz).stored) { + else if (member hasFlag MODULE) { + val moduleVar = newModuleVarMember(member) + List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) + } + else if (member hasFlag LAZY) { + val mixedinLazy = cloneAccessor() + val lazyVar = newLazyVarMember(mixedinLazy) + // println(s"mixing in lazy var: $lazyVar for $member") + List(lazyVar, accessorSymbolSynth.newSlowPathSymbol(mixedinLazy), newSuperLazy(mixedinLazy, site, lazyVar)) + } + else if (member.isGetter && !fieldMemoizationIn(member, clazz).constantTyped) { // add field if needed val field = clazz.newValue(member.localName, member.pos) setInfo fieldTypeForGetterIn(member, clazz.thisType) @@ -376,27 +465,34 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } else List(cloneAccessor()) // no field needed (constant-typed getter has constant as its RHS) } -// println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields") + // println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields") // omit fields that are not memoized, retain all other members - def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && !fieldMemoizationIn(sym, clazz).stored + def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && fieldMemoizationIn(sym, clazz).constantTyped val newDecls = - if (expandedModules.isEmpty && mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) + // under -Xcheckinit we generate all kinds of bitmaps, even when there are no lazy vals + if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty && !settings.checkInit) + oldDecls.filterNot(omittableField) else { // must not alter `decls` directly val newDecls = newScope val enter = newDecls enter (_: Symbol) val enterAll = (_: List[Symbol]) foreach enter + expandedModulesAndLazyVals foreach enter oldDecls foreach { d => if (!omittableField(d)) enter(d) } - expandedModules foreach enter mixedInAccessorAndFields foreach enterAll + // both oldDecls and mixedInAccessorAndFields (a list of lists) contribute + val bitmapSyms = accessorSymbolSynth.computeBitmapInfos(newDecls.toList) + + bitmapSyms foreach enter + newDecls } -// println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields") + // println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields") if (newDecls eq oldDecls) tp else ClassInfoType(parents, newDecls, clazz) @@ -408,53 +504,129 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // done by uncurry's info transformer // instead of forcing every member's info to run said transformer, duplicate the flag update logic... - def nonStaticModuleToMethod(module: Symbol): Unit = { + def nonStaticModuleToMethod(module: Symbol): Unit = if (!module.isStatic) module setFlag METHOD | STABLE - } - class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - def mkTypedUnit(pos: Position) = localTyper.typedPos(pos)(CODE.UNIT) + class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with CheckedAccessorTreeSynthesis { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) + + def mkTypedUnit(pos: Position) = typedPos(pos)(CODE.UNIT) + // TODO: clean up. this method is not used def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos)) - def mkAccessor(accessor: Symbol)(body: Tree) = localTyper.typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + def mkAccessor(accessor: Symbol)(body: Tree) = typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + + // this makes trees for mixed in fields, as well as for bitmap fields (their RHS will be EmptyTree because they are initialized implicitly) + // if we decide to explicitly initialize, use this RHS: if (symbol.info.typeSymbol.asClass == BooleanClass) FALSE else ZERO) + // could detect it's a bitmap field with something like `sym.name.startsWith(nme.BITMAP_PREFIX)` (or perhaps something more robust...) + def mkTypedValDef(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef] + + /** + * Desugar a local `lazy val x: Int = rhs` into + * ``` + * val x$lzy = new scala.runtime.LazyInt() + * def x$lzycompute(): Int = + * x$lzy.synchronized { + * if (x$lzy.initialized()) x$lzy.value() + * else { + * x$lzy.initialized = true + * x$lzy.value = rhs + * } + * } + * def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute() + * ``` + */ + private def mkLazyLocalDef(lazyVal: Symbol, rhs: Tree): Tree = { + import CODE._ + import scala.reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING + val owner = lazyVal.owner + + val lazyValType = lazyVal.tpe.resultType + val refClass = lazyHolders.getOrElse(lazyValType.typeSymbol, LazyRefClass) + val refTpe = if (refClass != LazyRefClass) refClass.tpe else appliedType(refClass.typeConstructor, List(lazyValType)) + + val lazyName = lazyVal.name.toTermName + val pos = lazyVal.pos.focus + + // used twice: once in the same owner as the lazy val, another time inside the compute method + val localLazyName = lazyName append LAZY_LOCAL_SUFFIX_STRING + + // The lazy holder val need not be mutable, as we write to its field. + // In fact, it MUST not be mutable to avoid capturing it as an ObjectRef in lambdalift + // Must be marked LAZY to allow forward references, as in `def test2 { println(s.length) ; lazy val s = "abc" } + val holderSym = owner.newValue(localLazyName, pos, LAZY | ARTIFACT) setInfo refTpe + + val initializedGetter = refTpe.member(nme.initialized) + val isUnit = refClass == LazyUnitClass + val valueGetter = if (isUnit) NoSymbol else refTpe.member(nme.value) + def getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) + + def mkChecked(alreadyComputed: Tree, compute: Tree) = If(Ident(holderSym) DOT initializedGetter, alreadyComputed, compute) + + val computerSym = + owner.newMethod(lazyName append nme.LAZY_SLOW_SUFFIX, pos, ARTIFACT | PRIVATE) setInfo MethodType(Nil, lazyValType) + + val rhsAtComputer = rhs.changeOwner(lazyVal -> computerSym) + val computer = mkAccessor(computerSym) { + val setInitialized = Apply(Select(Ident(holderSym), initializedGetter.setterIn(refClass)), TRUE :: Nil) + + if (isUnit) + gen.mkSynchronized(Ident(holderSym))(mkChecked(alreadyComputed = UNIT, compute = Block(List(rhsAtComputer, setInitialized), UNIT))) + else { + val valueSetter = Select(Ident(holderSym), valueGetter.setterIn(refClass)) + + gen.mkSynchronized(Ident(holderSym))(mkChecked( + alreadyComputed = getValue, + compute = Block(Apply(valueSetter, rhsAtComputer :: Nil) :: setInitialized :: Nil, getValue)) + ) + } + } + + val accessor = mkAccessor(lazyVal)(mkChecked(getValue, Apply(Ident(computerSym), Nil))) - def mkField(sym: Symbol) = localTyper.typedPos(sym.pos)(ValDef(sym)).asInstanceOf[ValDef] + // do last! + // remove STABLE: prevent replacing accessor call of type Unit by BoxedUnit.UNIT in erasure + // remove ACCESSOR: prevent constructors from eliminating the method body if the lazy val is + // lifted into a trait (TODO: not sure about the details here) + lazyVal.resetFlag(STABLE | ACCESSOR) + Thicket(mkTypedValDef(holderSym, New(refTpe)) :: computer :: accessor :: Nil) + } // synth trees for accessors/fields and trait setters when they are mixed into a class def fieldsAndAccessors(clazz: Symbol): List[ValOrDefDef] = { - def fieldAccess(accessor: Symbol): Option[Tree] = { + def fieldAccess(accessor: Symbol): List[Tree] = { val fieldName = accessor.localName val field = clazz.info.decl(fieldName) // The `None` result denotes an error, but it's refchecks' job to report it (this fallback is for robustness). // This is the result of overriding a val with a def, so that no field is found in the subclass. - if (field.exists) Some(Select(This(clazz), field)) - else None + if (field.exists) List(Select(This(clazz), field)) + else Nil } - def getterBody(getter: Symbol): Option[Tree] = { + def getterBody(getter: Symbol): List[Tree] = { // accessor created by newMatchingModuleAccessor for a static module that does need an accessor // (because there's a matching member in a super class) if (getter.asTerm.referenced.isModule) { - Some(gen.mkAttributedRef(clazz.thisType, getter.asTerm.referenced)) + List(gen.mkAttributedRef(clazz.thisType, getter.asTerm.referenced)) } else { val fieldMemoization = fieldMemoizationIn(getter, clazz) - if (fieldMemoization.pureConstant) Some(gen.mkAttributedQualifier(fieldMemoization.tp)) // TODO: drop when we no longer care about producing identical bytecode + if (fieldMemoization.constantTyped) List(gen.mkAttributedQualifier(fieldMemoization.tp)) // TODO: drop when we no longer care about producing identical bytecode else fieldAccess(getter) } } // println(s"accessorsAndFieldsNeedingTrees for $templateSym: $accessorsAndFieldsNeedingTrees") - def setterBody(setter: Symbol): Option[Tree] = { + def setterBody(setter: Symbol): List[Tree] = { // trait setter in trait - if (clazz.isTrait) Some(EmptyTree) + if (clazz.isTrait) List(EmptyTree) // trait setter for overridden val in class - else if (checkAndClearOverriddenTraitSetter(setter)) Some(mkTypedUnit(setter.pos)) + else if (checkAndClearOverriddenTraitSetter(setter)) List(mkTypedUnit(setter.pos)) // trait val/var setter mixed into class else fieldAccess(setter) map (fieldSel => Assign(fieldSel, Ident(setter.firstParam))) } - def moduleAccessorBody(module: Symbol): Some[Tree] = Some( + def moduleAccessorBody(module: Symbol): List[Tree] = List( // added during synthFieldsAndAccessors using newModuleAccessor // a module defined in a trait by definition can't be static (it's a member of the trait and thus gets a new instance for every outer instance) if (clazz.isTrait) EmptyTree @@ -462,11 +634,20 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else moduleInit(module) ) + val synthAccessorInClass = new SynthLazyAccessorsIn(clazz) + def superLazy(getter: Symbol): List[ValOrDefDef] = { + assert(!clazz.isTrait) + // this contortion was the only way I can get the super select to be type checked correctly.. TODO: why does SelectSuper not work? + val rhs = Apply(Select(Super(This(clazz), tpnme.EMPTY), getter.name), Nil) + explodeThicket(synthAccessorInClass.expandLazyClassMember(lazyVarOf(getter), getter, rhs, Map.empty)).asInstanceOf[List[ValOrDefDef]] + } + clazz.info.decls.toList.filter(checkAndClearNeedsTrees) flatMap { case module if module hasAllFlags (MODULE | METHOD) => moduleAccessorBody(module) map mkAccessor(module) + case getter if getter hasAllFlags (LAZY | METHOD) => superLazy(getter) case setter if setter.isSetter => setterBody(setter) map mkAccessor(setter) case getter if getter.hasFlag(ACCESSOR) => getterBody(getter) map mkAccessor(getter) - case field if !(field hasFlag METHOD) => Some(mkField(field)) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES) + case field if !(field hasFlag METHOD) => Some(mkTypedValDef(field)) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES) case _ => None } } @@ -474,10 +655,8 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def rhsAtOwner(stat: ValOrDefDef, newOwner: Symbol): Tree = atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner))) - - private def Thicket(trees: List[Tree]) = Block(trees, EmptyTree) override def transform(stat: Tree): Tree = { - val clazz = currentOwner + val currOwner = currentOwner // often a class, but not necessarily val statSym = stat.symbol /* @@ -500,22 +679,37 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // also remove ACCESSOR flag since there won't be an underlying field to access? case DefDef(_, _, _, _, _, rhs) if (statSym hasFlag ACCESSOR) && (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) - && !clazz.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo - && fieldMemoizationIn(statSym, clazz).pureConstant => - deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) // TODO: recurse? + && !currOwner.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo + && fieldMemoizationIn(statSym, currOwner).constantTyped => + deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) + + // deferred val, trait val, lazy val (local or in class) + case vd@ValDef(mods, name, tpt, rhs) if vd.symbol.hasFlag(ACCESSOR) && treeInfo.noFieldFor(vd, currOwner) => + val transformedRhs = atOwner(statSym)(transform(rhs)) + + if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) + else if (currOwner.isTrait) mkAccessor(statSym)(transformedRhs) + else if (!currOwner.isClass) mkLazyLocalDef(vd.symbol, transformedRhs) + else { + // TODO: make `synthAccessorInClass` a field and update it in atOwner? + // note that `LazyAccessorTreeSynth` is pretty lightweight + // (it's just a bunch of methods that all take a `clazz` parameter, which is thus stored as a field) + val synthAccessorInClass = new SynthLazyAccessorsIn(currOwner) + synthAccessorInClass.expandLazyClassMember(lazyVarOf(statSym), statSym, transformedRhs, nullables.getOrElse(currOwner, Map.empty)) + } // drop the val for (a) constant (pure & not-stored) and (b) not-stored (but still effectful) fields case ValDef(mods, _, _, rhs) if (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) - && fieldMemoizationIn(statSym, clazz).pureConstant => - EmptyTree + && fieldMemoizationIn(statSym, currOwner).constantTyped => + EmptyThicket case ModuleDef(_, _, impl) => // ??? The typer doesn't take kindly to seeing this ClassDef; we have to set NoType so it will be ignored. val cd = super.transform(ClassDef(statSym.moduleClass, impl) setType NoType) - if (clazz.isClass) cd + if (currOwner.isClass) cd else { // local module -- symbols cannot be generated by info transformer, so do it all here - val moduleVar = newModuleVarSymbol(currentOwner, statSym, statSym.info.resultType, 0) - Thicket(cd :: mkField(moduleVar) :: mkAccessor(statSym)(moduleInit(statSym)) :: Nil) + val moduleVar = newModuleVarSymbol(currOwner, statSym, statSym.info.resultType) + Thicket(cd :: mkTypedValDef(moduleVar) :: mkAccessor(statSym)(moduleInit(statSym)) :: Nil) } case tree => @@ -524,26 +718,29 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } } + def transformTermsAtExprOwner(exprOwner: Symbol)(stat: Tree) = if (stat.isTerm) atOwner(exprOwner)(transform(stat)) else transform(stat) + private val nullables = perRunCaches.newMap[Symbol, Map[Symbol, List[Symbol]]] + override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { val addedStats = - if (!currentOwner.isClass) Nil + if (!currentOwner.isClass) Nil // TODO: || currentOwner.isPackageClass else afterOwnPhase { fieldsAndAccessors(currentOwner) } + val inRealClass = currentOwner.isClass && !(currentOwner.isPackageClass || currentOwner.isTrait) + if (inRealClass) + nullables(currentOwner) = lazyValNullables(currentOwner, stats) + val newStats = stats mapConserve (if (exprOwner != currentOwner) transformTermsAtExprOwner(exprOwner) else transform) addedStats ::: (if (newStats eq stats) stats else { // check whether we need to flatten thickets and drop empty ones - if (newStats exists { case EmptyTree => true case Block(_, EmptyTree) => true case _ => false }) - newStats flatMap { - case EmptyTree => Nil - case Block(thicket, EmptyTree) => thicket - case stat => stat :: Nil - } + if (newStats exists mustExplodeThicket) + newStats flatMap explodeThicket else newStats }) } diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala deleted file mode 100644 index fc7999bf3b..0000000000 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ /dev/null @@ -1,318 +0,0 @@ -package scala.tools.nsc -package transform - -import scala.collection.mutable - -abstract class LazyVals extends Transform with TypingTransformers with ast.TreeDSL { - // inherits abstract value `global` and class `Phase` from Transform - - import global._ // the global environment - import definitions._ // standard classes and methods - import typer.typed // methods to type trees - import CODE._ - - val phaseName: String = "lazyvals" - private val FLAGS_PER_BYTE: Int = 8 // Byte - private def bitmapKind = ByteClass - - def newTransformer(unit: CompilationUnit): Transformer = - new LazyValues(unit) - - private def lazyUnit(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass - - object LocalLazyValFinder extends Traverser { - var result: Boolean = _ - - def find(t: Tree) = {result = false; traverse(t); result} - def find(ts: List[Tree]) = {result = false; traverseTrees(ts); result} - - override def traverse(t: Tree) { - if (!result) - t match { - case v@ValDef(_, _, _, _) if v.symbol.isLazy => - result = true - - case d@DefDef(_, _, _, _, _, _) if d.symbol.isLazy && lazyUnit(d.symbol) => - d.symbol.resetFlag(symtab.Flags.LAZY) - result = true - - case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) => - - // Avoid adding bitmaps when they are fully overshadowed by those that are added inside loops - case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) => - - case _ => - super.traverse(t) - } - } - } - - /** - * Transform local lazy accessors to check for the initialized bit. - */ - class LazyValues(unit: CompilationUnit) extends TypingTransformer(unit) { - /** map from method symbols to the number of lazy values it defines. */ - private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0 - - import symtab.Flags._ - private def flattenThickets(stats: List[Tree]): List[Tree] = stats.flatMap(_ match { - case b @ Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if b.tpe == null && n1.endsWith(nme.LAZY_SLOW_SUFFIX) => - List(d1, d2) - case stat => - List(stat) - }) - - /** Perform the following transformations: - * - for a lazy accessor inside a method, make it check the initialization bitmap - * - implement double checked locking of member modules for non-trait owners (trait just have the abstract accessor) - * ``` - * // typer - * class C { object x } - * // fields - * class C { var x$module; def x() = { x$module = new x; x$module } - * // lazyvals - * class C { - * var x$module // module var - * def x() = { if (x$module == null) x$lzycompute() else x$module // fast path - * def x$lzycompute() = { synchronized { if (x$module == null) x$module = new x }; x$module } // slow path - * } - * ``` - * - for all methods, add enough int vars to allow one flag per lazy local value - * - blocks in template bodies behave almost like methods. A single bitmaps section is - * added in the first block, for all lazy values defined in such blocks. - * - remove ACCESSOR flags: accessors in traits are not statically implemented, - * but moved to the host class. local lazy values should be statically implemented. - */ - override def transform(tree: Tree): Tree = { - val sym = tree.symbol - curTree = tree - - tree match { - - case Block(_, _) => - val block1 = super.transform(tree) - val Block(stats, expr) = block1 - treeCopy.Block(block1, flattenThickets(stats), expr) - - case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) { - val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) { - val enclosingClassOrDummyOrMethod = { - val enclMethod = sym.enclMethod - - if (enclMethod != NoSymbol) { - val enclClass = sym.enclClass - if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod) - enclClass - else - enclMethod - } else - sym.owner - } - debuglog(s"determined enclosing class/dummy/method for lazy val as $enclosingClassOrDummyOrMethod given symbol $sym") - val idx = lazyVals(enclosingClassOrDummyOrMethod) - lazyVals(enclosingClassOrDummyOrMethod) = idx + 1 - val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym) - sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR) - (rhs1, sDef) - } else if (sym.hasAllFlags(MODULE | METHOD) && !sym.owner.isTrait) { - rhs match { - case b @ Block((assign @ Assign(moduleRef, _)) :: Nil, expr) => - def cond = Apply(Select(moduleRef, Object_eq), List(Literal(Constant(null)))) - val (fastPath, slowPath) = mkFastPathBody(sym.owner.enclClass, moduleRef.symbol, cond, transform(assign) :: Nil, Nil, transform(expr)) - (localTyper.typedPos(tree.pos)(fastPath), localTyper.typedPos(tree.pos)(slowPath)) - case rhs => - global.reporter.error(tree.pos, "Unexpected tree on the RHS of a module accessor: " + rhs) - (rhs, EmptyTree) - } - } else { - (transform(rhs), EmptyTree) - } - - val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res) - if (slowPathDef != EmptyTree) { - // The contents of this block are flattened into the enclosing statement sequence, see flattenThickets - // This is a poor man's version of dotty's Thicket: https://github.com/lampepfl/dotty/blob/d5280358d1/src/dotty/tools/dotc/ast/Trees.scala#L707 - Block(slowPathDef, ddef1) - } else ddef1 - } - - case Template(_, _, body) => atOwner(currentOwner) { - // TODO: shady business... can this logic be encapsulated in LocalLazyValFinder? - var added = false - val stats = super.transformTrees(body) mapConserve { - case stat: ValDef => typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) - case stat: TermTree if !added && (LocalLazyValFinder find stat) => - added = true - typed(addBitmapDefs(sym, stat)) - case stat => stat - } - - val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) { - // add bitmap to inner class if necessary - val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO))) - toAdd0.foreach(t => { - if (currentOwner.info.decl(t.symbol.name) == NoSymbol) { - t.symbol.setFlag(PROTECTED) - currentOwner.info.decls.enter(t.symbol) - } - }) - toAdd0 - } else List() - deriveTemplate(tree)(_ => innerClassBitmaps ++ flattenThickets(stats)) - } - - case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass => - deriveValDef(tree) { rhs0 => - val rhs = transform(rhs0) - if (LocalLazyValFinder.find(rhs)) typed(addBitmapDefs(sym, rhs)) else rhs - } - - case l@LabelDef(name0, params0, ifp0@If(_, _, _)) if name0.startsWith(nme.WHILE_PREFIX) => - val ifp1 = super.transform(ifp0) - val If(cond0, thenp0, elsep0) = ifp1 - - if (LocalLazyValFinder.find(thenp0)) - deriveLabelDef(l)(_ => treeCopy.If(ifp1, cond0, typed(addBitmapDefs(sym.owner, thenp0)), elsep0)) - else - l - - case l@LabelDef(name0, params0, block@Block(stats0, expr)) - if name0.startsWith(nme.WHILE_PREFIX) || name0.startsWith(nme.DO_WHILE_PREFIX) => - val stats1 = super.transformTrees(stats0) - if (LocalLazyValFinder.find(stats1)) - deriveLabelDef(l)(_ => treeCopy.Block(block, typed(addBitmapDefs(sym.owner, stats1.head))::stats1.tail, expr)) - else - l - - case _ => super.transform(tree) - } - } - - /** Add the bitmap definitions to the rhs of a method definition. - * If the rhs has been tail-call transformed, insert the bitmap - * definitions inside the top-level label definition, so that each - * iteration has the lazy values uninitialized. Otherwise add them - * at the very beginning of the method. - */ - private def addBitmapDefs(methSym: Symbol, rhs: Tree): Tree = { - def prependStats(stats: List[Tree], tree: Tree): Block = tree match { - case Block(stats1, res) => Block(stats ::: stats1, res) - case _ => Block(stats, tree) - } - - val bmps = bitmaps(methSym) map (ValDef(_, ZERO)) - - def isMatch(params: List[Ident]) = (params.tail corresponds methSym.tpe.params)(_.tpe == _.tpe) - - if (bmps.isEmpty) rhs else rhs match { - case Block(assign, l @ LabelDef(name, params, _)) - if (name string_== "_" + methSym.name) && isMatch(params) => - Block(assign, deriveLabelDef(l)(rhs => typed(prependStats(bmps, rhs)))) - - case _ => prependStats(bmps, rhs) - } - } - - def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): Tree = { - val owner = lzyVal.owner - val defSym = owner.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) - defSym setInfo MethodType(List(), lzyVal.tpe.resultType) - if (owner.isClass) owner.info.decls.enter(defSym) - debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") - // this is a hack i don't understand for lazy vals nested in a lazy val, introduced in 3769f4d, - // tested in pos/t3670 (add9be64). class A { val n = { lazy val b = { lazy val dd = 3; dd }; b } } - // bitmaps has an entry bMethodSym -> List(bitmap$0), where bitmap$0.owner == bMethodSym. - // now we set bitmap$0.owner = b$lzycomputeMethodSym. - for (bitmap <- bitmaps(lzyVal)) bitmap.owner = defSym - val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym) - - DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) - } - - - def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): (Tree, Tree) = { - val slowPathDef: Tree = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal) - (If(cond, Apply(Ident(slowPathDef.symbol), Nil), retVal), slowPathDef) - } - - /** return a 'lazified' version of rhs. Rhs should conform to the - * following schema: - * { - * l$ = <rhs> - * l$ - * } or - * <rhs> when the lazy value has type Unit (for which there is no field - * to cache its value. - * - * Similarly as for normal lazy val members (see Mixin), the result will be a tree of the form - * { if ((bitmap&n & MASK) == 0) this.l$compute() - * else l$ - * - * def l$compute() = { synchronized(enclosing_class_or_dummy) { - * if ((bitmap$n & MASK) == 0) { - * l$ = <rhs> - * bitmap$n = bimap$n | MASK - * }} - * l$ - * } - * } - * where bitmap$n is a byte value acting as a bitmap of initialized values. It is - * the 'n' is (offset / 8), the MASK is (1 << (offset % 8)). If the value has type - * unit, no field is used to cache the value, so the l$compute will now look as following: - * { - * def l$compute() = { synchronized(enclosing_class_or_dummy) { - * if ((bitmap$n & MASK) == 0) { - * <rhs>; - * bitmap$n = bimap$n | MASK - * }} - * () - * } - * } - */ - private def mkLazyDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = { - val bitmapSym = getBitmapFor(methOrClass, offset) - val mask = LIT(1 << (offset % FLAGS_PER_BYTE)) - val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym) - - def mkBlock(stmt: Tree) = BLOCK(stmt, mkSetFlag(bitmapSym, mask, bitmapRef), UNIT) - - debuglog(s"create complete lazy def in $methOrClass for $lazyVal") - val (block, res) = tree match { - case Block(List(assignment), res) if !lazyUnit(lazyVal) => - (mkBlock(assignment), res) - case rhs => - (mkBlock(rhs), UNIT) - } - - def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) - val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) - (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 })) - } - - private def mkSetFlag(bmp: Symbol, mask: Tree, bmpRef: Tree): Tree = - bmpRef === (bmpRef GEN_| (mask, bitmapKind)) - - val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil - - /** Return the symbol corresponding of the right bitmap int inside meth, - * given offset. - */ - private def getBitmapFor(meth: Symbol, offset: Int): Symbol = { - val n = offset / FLAGS_PER_BYTE - val bmps = bitmaps(meth) - if (bmps.length > n) - bmps(n) - else { - val sym = meth.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), meth.pos).setInfo(ByteTpe) - enteringTyper { - sym addAnnotation VolatileAttr - } - - bitmaps(meth) = (sym :: bmps).reverse - sym - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index f781426f1a..d462b00261 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 */ @@ -9,13 +10,14 @@ package transform import symtab._ import Flags._ 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" @@ -57,8 +59,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { */ 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 ----------------------------------------------- @@ -71,47 +71,22 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * (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.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) + && (!(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)) ) - 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) - ) /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * @@ -156,7 +131,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { ) } - private def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass /** Add given member to given class, and mark member as mixed-in. */ @@ -207,9 +181,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else { assert(member.isTerm && !member.isDeferred, member) // disable assert to support compiling against code compiled by an older compiler (until we re-starr) - // assert(member hasFlag LAZY | PRESUPER, s"unexpected $member in $clazz ${member.debugFlagString}") - // lazy vals still leave field symbols lying around in traits -- TODO: never emit them to begin with - // ditto for early init vals + // assert(member hasFlag PRESUPER, s"unexpected $member in $clazz ${member.debugFlagString}") clazz.info.decls.unlink(member) } @@ -295,9 +267,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - /* 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: @@ -319,28 +289,15 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember) - && (mixinMember hasFlag (LAZY | PARAMACCESSOR)) + && (mixinMember hasFlag PARAMACCESSOR) && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { - // pick up where `fields` left off -- it already mixed in fields and accessors for regular vals. - // but has ignored lazy vals and constructor parameter accessors - // TODO: captures added by lambdalift for local traits? - // - // mixin accessor for lazy val or constructor parameter + // mixin accessor for constructor parameter // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) - val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) + cloneAndAddMixinMember(mixinClass, mixinMember) + val name = mixinMember.name - if (mixinMember.isLazy) - initializer(mixedInAccessor) = - (mixinClass.info.decl(name) orElse abort(s"Could not find initializer for lazy val $name!")) - - // Add field while we're mixing in the getter (unless it's a Unit-typed lazy val) - // - // lazy val of type Unit doesn't need a field -- the bitmap is enough. - // TODO: constant-typed lazy vals... it's an extreme corner case, but we could also suppress the field in: - // `trait T { final lazy val a = "a" }; class C extends T`, but who writes code like that!? :) - // we'd also have to change the lazyvals logic if we do this - if (!nme.isSetterName(name) && !(mixinMember.isLazy && isUnitGetter(mixinMember))) { + 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) @@ -354,7 +311,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val newFlags = ( (PrivateLocal) - | (mixinMember getFlag MUTABLE | LAZY) + | (mixinMember getFlag MUTABLE) | (if (mixinMember.hasStableFlag) 0 else MUTABLE) ) @@ -384,82 +341,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { override def transformInfo(sym: Symbol, tp: Type): Type = 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 - } - } - // --------- term transformation ----------------------------------------------- protected def newTransformer(unit: CompilationUnit): Transformer = new MixinTransformer(unit) - class MixinTransformer(unit : CompilationUnit) extends Transformer { + 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 - } - /** The first transform; called in a pre-order traversal at phase mixin * (that is, every node is processed before its children). @@ -484,416 +379,24 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - 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 - } - } /** 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 - */ + * + * 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 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(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 _ => - 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) - } - - sym setFlag PrivateLocal - clazz0.info.decls.enter(sym) - addDef(clazz0.pos, init) - sym - } - - 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 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)) - } - - /* 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 isEmpty = stat.rhs == EmptyTree - - if (!clazz.isTrait && sym.isLazy && !isEmpty) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnitGetter(sym) => 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(TRAIT)) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat)(rhs => - (mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))( - if (isUnitGetter(sym)) 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 stat - } - stats map { - case defn: DefDef => dd(defn) - case stat => stat - } - } - - 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 - } - 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 - ) - } - } - - /* 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 - } - - 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) - } - } - buildBitmapOffsets() - var stats1 = addCheckedGetters(clazz, stats) - - def getterBody(getter: Symbol) = { - assert(getter.isGetter) - val readValue = - if (getter.isLazy) { - getter.tpe.resultType match { - case ConstantType(c) => Literal(c) - case _ => - val initCall = Apply(SuperSelect(clazz, initializer(getter)), Nil) - val offset = fieldOffset(getter) - if (isUnitGetter(getter)) mkLazyDef(clazz, getter, List(initCall), UNIT, offset) - else mkLazyDef(clazz, getter, List(atPos(getter.pos)(Assign(fieldAccess(getter), initCall))), fieldAccess(getter), offset) - } - } else { - assert(getter.hasFlag(PARAMACCESSOR)) - fieldAccess(getter) - } - - if (!needsInitFlag(getter)) readValue - else mkCheckedAccessor(clazz, readValue, fieldOffset(getter), getter.pos, getter) - } - - def setterBody(setter: Symbol) = { - val getter = setter.getterIn(clazz) - assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz") - - val setInitFlag = - if (!needsInitFlag(getter)) Nil - else List(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter))) - - Block(Assign(fieldAccess(setter), Ident(setter.firstParam)) :: setInitFlag : _*) - } - - def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed) - + val accessorSynth = new UncheckedAccessorSynth(clazz) + import accessorSynth._ // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { @@ -902,10 +405,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { 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 (LAZY | PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/param?!?") + assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not param?!?") // add accessor definitions - addDefDef(sym, if (sym.isSetter) setterBody(sym) else getterBody(sym)) + addDefDef(sym, accessorBody(sym)) } else if (!sym.isMethod) addValDef(sym) // field else if (!sym.isMacro) { // forwarder @@ -915,32 +418,33 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - stats1 = add(stats1, newDefs.toList) + val implementedAccessors = implementWithNewDefs(stats) - if (clazz.isTrait) stats1 = stats1.filter { - case vd: ValDef => - assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR | LAZY), s"unexpected valdef $vd in trait $clazz") - false + if (clazz.isTrait) + implementedAccessors filter { + case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false case _ => true } + 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 _ => + stat + } - 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 + implementedAccessors map completeSuperAccessor } - else Map() } /** The transform that gets applied to a tree after it has been completely @@ -961,8 +465,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { 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 val statsWithNewDefs = addNewDefs(currentOwner, body) statsWithNewDefs foreach { diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 87c14eb3a1..c171050bbd 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -723,7 +723,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) { forwardToOverload(m) - } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition + } else if (m.isValue && !m.isMethod) { // concrete value definition def mkAccessor(field: Symbol, name: Name) = { val newFlags = (SPECIALIZED | m.getterIn(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR) // we rely on the super class to initialize param accessors @@ -744,7 +744,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { enterMember(specVal) // create accessors - if (nme.isLocalName(m.name)) { + if (m.isLazy) { + // no getters needed (we'll specialize the compute method and accessor separately), can stay private + // m.setFlag(PRIVATE) -- TODO: figure out how to leave the non-specialized lazy var private + // (the implementation needs it to be visible while duplicating and retypechecking, + // but it really could be private in bytecode) + specVal.setFlag(PRIVATE) + } + else if (nme.isLocalName(m.name)) { val specGetter = mkAccessor(specVal, specVal.getterName) setInfo MethodType(Nil, specVal.info) val origGetter = overrideIn(sClass, m.getterIn(clazz)) info(origGetter) = Forward(specGetter) diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index fa7c503213..744b9c8a8e 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -274,10 +274,8 @@ abstract class TailCalls extends Transform { import runDefinitions.{Boolean_or, Boolean_and} tree match { - case ValDef(_, _, _, _) => - if (tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass)) - reporter.error(tree.pos, "lazy vals are not tailcall transformed") - + case dd: DefDef if tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass) => + reporter.error(tree.pos, "lazy vals are not tailcall transformed") super.transform(tree) case dd @ DefDef(_, name, _, vparamss0, _, rhs0) if isEligible(dd) => diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 6ade45c41c..f6c667353f 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -439,9 +439,9 @@ abstract class UnCurry extends InfoTransform super.transform(treeCopy.DefDef(dd, mods, name, tparams, vparamssNoRhs, tpt, rhs)) } } - case ValDef(_, _, _, rhs) => + case ValDef(mods, _, _, rhs) => if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit) - if (!sym.owner.isSourceMethod) + if (!sym.owner.isSourceMethod || mods.isLazy) withNeedLift(needLift = true) { super.transform(tree) } else super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 78e72cf771..df014b5161 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -151,8 +151,12 @@ abstract class Duplicators extends Analyzer { ldef.symbol = newsym debuglog("newsym: " + newsym + " info: " + newsym.info) - case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) => - debuglog("ValDef " + name + " sym.info: " + vdef.symbol.info) + // don't retypecheck val members or local lazy vals -- you'll end up with duplicate symbols because + // entering a valdef results in synthesizing getters etc + // TODO: why retype check any valdefs?? I checked and the rhs is specialized just fine this way + // (and there are no args/type params/... to warrant full type checking?) + case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) || owner.isClass => + debuglog(s"ValDef $name in $owner sym.info: ${vdef.symbol.info}") invalidSyms(vdef.symbol) = vdef val newowner = owner orElse context.owner val newsym = vdef.symbol.cloneSymbol(newowner) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index e0b64a7600..d11417192d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -5,6 +5,7 @@ package scala.tools.nsc package typechecker +import scala.reflect.NameTransformer import symtab.Flags._ import scala.reflect.internal.util.StringOps.ojoin import scala.reflect.internal.util.ListOfNil @@ -116,38 +117,53 @@ trait MethodSynthesis { import NamerErrorGen._ - def enterImplicitWrapper(tree: ClassDef): Unit = { - enterSyntheticSym(ImplicitClassWrapper(tree).derivedTree) - } - // trees are later created by addDerivedTrees (common logic is encapsulated in field/standardAccessors/beanAccessors) + import treeInfo.noFieldFor + + // populate synthetics for this unit with trees that will later be added by the typer + // we get here when entering the symbol for the valdef, so its rhs has not yet been type checked def enterGetterSetter(tree: ValDef): Unit = { + val fieldSym = + if (noFieldFor(tree, owner)) NoSymbol + else owner.newValue(tree.name append NameTransformer.LOCAL_SUFFIX_STRING, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal) + val getter = Getter(tree) val getterSym = getter.createSym - val setterSym = if (getter.needsSetter) Setter(tree).createSym else NoSymbol - - // a lazy field is linked to its lazy accessor (TODO: can we do the same for field -> getter -> setter) - val fieldSym = if (Field.noFieldFor(tree)) NoSymbol else Field(tree).createSym(getterSym) // only one symbol can have `tree.pos`, the others must focus their position // normally the field gets the range position, but if there is none, give it to the getter tree.symbol = fieldSym orElse (getterSym setPos tree.pos) + val namer = namerOf(tree.symbol) + + // the valdef gets the accessor symbol for a lazy val (too much going on in its RHS) + // the fields phase creates the field symbol + if (!tree.mods.isLazy) { + // if there's a field symbol, the getter is considered a synthetic that must be added later + // if there's no field symbol, the ValDef tree receives the getter symbol and thus is not a synthetic + if (fieldSym != NoSymbol) { + context.unit.synthetics(getterSym) = getter.derivedTree(getterSym) + getterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = false) + } else getterSym setInfo namer.valTypeCompleter(tree) + + enterInScope(getterSym) + + if (getter.needsSetter) { + val setter = Setter(tree) + val setterSym = setter.createSym + context.unit.synthetics(setterSym) = setter.derivedTree(setterSym) + setterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = true) + enterInScope(setterSym) + } - val namer = if (fieldSym != NoSymbol) namerOf(fieldSym) else namerOf(getterSym) - - // There's no reliable way to detect all kinds of setters from flags or name!!! - // A BeanSetter's name does not end in `_=` -- it does begin with "set", but so could the getter - // for a regular Scala field... TODO: can we add a flag to distinguish getter/setter accessors? - val getterCompleter = namer.accessorTypeCompleter(tree, isSetter = false) - val setterCompleter = namer.accessorTypeCompleter(tree, isSetter = true) - - getterSym setInfo getterCompleter - setterSym andAlso (_ setInfo setterCompleter) - fieldSym andAlso (_ setInfo namer.valTypeCompleter(tree)) - - enterInScope(getterSym) - setterSym andAlso (enterInScope(_)) - fieldSym andAlso (enterInScope(_)) + // TODO: delay emitting the field to the fields phase (except for private[this] vals, which only get a field and no accessors) + if (fieldSym != NoSymbol) { + fieldSym setInfo namer.valTypeCompleter(tree) + enterInScope(fieldSym) + } + } else { + getterSym setInfo namer.valTypeCompleter(tree) + enterInScope(getterSym) + } deriveBeanAccessors(tree, namer) } @@ -188,242 +204,82 @@ trait MethodSynthesis { sym } - val getterCompleter = namer.beanAccessorTypeCompleter(tree, missingTpt, isSetter = false) + val getterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = false) enterInScope(deriveBeanAccessor(if (hasBeanProperty) "get" else "is") setInfo getterCompleter) if (tree.mods.isMutable) { - val setterCompleter = namer.beanAccessorTypeCompleter(tree, missingTpt, isSetter = true) + val setterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = true) enterInScope(deriveBeanAccessor("set") setInfo setterCompleter) } } } - import AnnotationInfo.{mkFilter => annotationFilter} - def addDerivedTrees(typer: Typer, stat: Tree): List[Tree] = stat match { - case vd @ ValDef(mods, name, tpt, rhs) if deriveAccessors(vd) && !vd.symbol.isModuleVar && !vd.symbol.isJava => - stat.symbol.initialize // needed! - - val getter = Getter(vd) - getter.validate() - val accessors = getter :: (if (getter.needsSetter) Setter(vd) :: Nil else Nil) - (Field(vd) :: accessors).map(_.derivedTree).filter(_ ne EmptyTree) - - case cd @ ClassDef(mods, _, _, _) if mods.isImplicit => - val annotations = stat.symbol.initialize.annotations - // TODO: need to shuffle annotations between wrapper and class. - val wrapper = ImplicitClassWrapper(cd) - val meth = wrapper.derivedSym - context.unit.synthetics get meth match { - case Some(mdef) => - context.unit.synthetics -= meth - meth setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) - cd.symbol setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) - List(cd, mdef) - case _ => - // Shouldn't happen, but let's give ourselves a reasonable error when it does - context.error(cd.pos, s"Internal error: Symbol for synthetic factory method not found among ${context.unit.synthetics.keys.mkString(", ")}") - // Soldier on for the sake of the presentation compiler - List(cd) - } - case _ => - stat :: Nil - } - - - sealed trait Derived { - /** The derived symbol. It is assumed that this symbol already exists and has been - * entered in the parent scope when derivedSym is called - */ - def derivedSym: Symbol - - /** The definition tree of the derived symbol. */ - def derivedTree: Tree + def enterImplicitWrapper(classDef: ClassDef): Unit = { + val methDef = factoryMeth(classDef.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef) + val methSym = assignAndEnterSymbol(methDef) + context.unit.synthetics(methSym) = methDef + methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol, completerOf(methDef).asInstanceOf[LockingTypeCompleter]) } - /** A synthetic method which performs the implicit conversion implied by - * the declaration of an implicit class. - */ - case class ImplicitClassWrapper(tree: ClassDef) extends Derived { - def derivedSym = { - val enclClass = tree.symbol.owner.enclClass - // Only methods will do! Don't want to pick up any stray - // companion objects of the same name. - val result = enclClass.info decl derivedName filter (x => x.isMethod && x.isSynthetic) - if (result == NoSymbol || result.isOverloaded) - context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $derivedName in $enclClass / ${enclClass.info.decls}") - result - } - - def derivedTree = factoryMeth(derivedMods, derivedName, tree) - - def derivedName = tree.name.toTermName - def derivedMods = tree.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC - } - - trait DerivedAccessor extends Derived { + trait DerivedAccessor { def tree: ValDef def derivedName: TermName def derivedFlags: Long + def derivedTree(sym: Symbol): Tree def derivedPos = tree.pos.focus def createSym = createMethod(tree, derivedName, derivedPos, derivedFlags) } case class Getter(tree: ValDef) extends DerivedAccessor { - def derivedName = tree.name - - def derivedSym = - if (tree.mods.isLazy) tree.symbol.lazyAccessor - else if (Field.noFieldFor(tree)) tree.symbol - else tree.symbol.getterIn(tree.symbol.enclClass) - + def derivedName = tree.name def derivedFlags = tree.mods.flags & GetterFlags | ACCESSOR.toLong | ( if (needsSetter) 0 else STABLE ) + def needsSetter = tree.mods.isMutable // implies !lazy - def needsSetter = tree.mods.isMutable // implies !lazy - - override def derivedTree = - if (tree.mods.isLazy) deriveLazyAccessor - else newDefDef(derivedSym, if (Field.noFieldFor(tree)) tree.rhs else Select(This(tree.symbol.enclClass), tree.symbol))(tpt = derivedTpt) - - /** Implements lazy value accessors: - * - for lazy values of type Unit and all lazy fields inside traits, - * the rhs is the initializer itself, because we'll just "compute" the result on every access - * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap) - * - for all other lazy values z the accessor is a block of this form: - * { z = <rhs>; z } where z can be an identifier or a field. - */ - private def deriveLazyAccessor: DefDef = { - val ValDef(_, _, tpt0, rhs0) = tree - val rhs1 = context.unit.transformed.getOrElse(rhs0, rhs0) - val body = - if (tree.symbol.owner.isTrait || Field.noFieldFor(tree)) rhs1 // TODO move tree.symbol.owner.isTrait into noFieldFor - else gen.mkAssignAndReturn(tree.symbol, rhs1) - - derivedSym setPos tree.pos // TODO: can we propagate `tree.pos` to `derivedSym` when the symbol is created? - val ddefRes = DefDef(derivedSym, new ChangeOwnerTraverser(tree.symbol, derivedSym)(body)) - // ValDef will have its position focused whereas DefDef will have original correct rangepos - // ideally positions would be correct at the creation time but lazy vals are really a special case - // here so for the sake of keeping api clean we fix positions manually in LazyValGetter - ddefRes.tpt.setPos(tpt0.pos) - tpt0.setPos(tpt0.pos.focus) - ddefRes - } + override def derivedTree(derivedSym: Symbol) = { + val missingTpt = tree.tpt.isEmpty + val tpt = if (missingTpt) TypeTree() else tree.tpt.duplicate - // TODO: more principled approach -- this is a bit bizarre - private def derivedTpt = { - // For existentials, don't specify a type for the getter, even one derived - // from the symbol! This leads to incompatible existentials for the field and - // the getter. Let the typer do all the work. You might think "why only for - // existentials, why not always," and you would be right, except: a single test - // fails, but it looked like some work to deal with it. Test neg/t0606.scala - // starts compiling (instead of failing like it's supposed to) because the typer - // expects to be able to identify escaping locals in typedDefDef, and fails to - // spot that brand of them. In other words it's an artifact of the implementation. - // - // JZ: ... or we could go back to uniformly using explicit result types in all cases - // if we fix `dropExistential`. More details https://github.com/scala/scala-dev/issues/165 - val getterTp = derivedSym.tpe_*.finalResultType - // Range position errors ensue if we don't duplicate this in some - // circumstances (at least: concrete vals with existential types.) - def inferredTpt = TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) - val tpt = getterTp match { - case _: ExistentialType => inferredTpt - case _ => getterTp.widen match { - case _: ExistentialType => inferredTpt - case _ if tree.mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field - case _ => TypeTree(getterTp) - } - } - tpt setPos tree.tpt.pos.focus - } + val rhs = + if (noFieldFor(tree, owner)) tree.rhs // context.unit.transformed.getOrElse(tree.rhs, tree.rhs) + else Select(This(tree.symbol.enclClass), tree.symbol) - def validate() = { - assert(derivedSym != NoSymbol, tree) - if (derivedSym.isOverloaded) - GetterDefinedTwiceError(derivedSym) + newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = Nil, tpt = tpt) } +// derivedSym setPos tree.pos +// // ValDef will have its position focused whereas DefDef will have original correct rangepos +// // ideally positions would be correct at the creation time but lazy vals are really a special case +// // here so for the sake of keeping api clean we fix positions manually in LazyValGetter +// tpt.setPos(tree.tpt.pos) +// tree.tpt.setPos(tree.tpt.pos.focus) + } case class Setter(tree: ValDef) extends DerivedAccessor { def derivedName = tree.setterName - def derivedSym = tree.symbol.setterIn(tree.symbol.enclClass) def derivedFlags = tree.mods.flags & SetterFlags | ACCESSOR - def derivedTree = - derivedSym.paramss match { - case (setterParam :: Nil) :: _ => - // assert(!derivedSym.isOverloaded, s"Unexpected overloaded setter $derivedSym for ${tree.symbol} in ${tree.symbol.enclClass}") - val rhs = - if (Field.noFieldFor(tree) || derivedSym.isOverloaded) EmptyTree - else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam)) - - DefDef(derivedSym, rhs) - case _ => EmptyTree - } - } - - object Field { - // No field for these vals (either never emitted or eliminated later on): - // - abstract vals have no value we could store (until they become concrete, potentially) - // - lazy vals of type Unit - // - concrete vals in traits don't yield a field here either (their getter's RHS has the initial value) - // Constructors will move the assignment to the constructor, abstracting over the field using the field setter, - // and Fields will add a field to the class that mixes in the trait, implementing the accessors in terms of it - // - [Emitted, later removed during Constructors] a concrete val with a statically known value (ConstantType) - // performs its side effect according to lazy/strict semantics, but doesn't need to store its value - // each access will "evaluate" the RHS (a literal) again - // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer. - // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does. - // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`), - // similarly, the `def field` call-site breaks when you add `|| vd.symbol.owner.isTrait` (detected in test suite) - // as the symbol info is in the process of being created then. - // TODO: harmonize tree & symbol creation - // the middle `&& !owner.isTrait` is needed after `isLazy` because non-unit-typed lazy vals in traits still get a field -- see neg/t5455.scala - def noFieldFor(vd: ValDef) = (vd.mods.isDeferred - || (vd.mods.isLazy && !owner.isTrait && isUnitType(vd.symbol.info)) - || (owner.isTrait && !traitFieldFor(vd))) - - // TODO: never emit any fields in traits -- only use getter for lazy/presuper ones as well - private def traitFieldFor(vd: ValDef): Boolean = vd.mods.hasFlag(PRESUPER | LAZY) - } + def derivedTree(derivedSym: Symbol) = { + val setterParam = nme.syntheticParamName(1) - case class Field(tree: ValDef) extends Derived { - private val isLazy = tree.mods.isLazy - - // If the owner is not a class, this is a lazy val from a method, - // with no associated field. It has an accessor with $lzy appended to its name and - // its flags are set differently. The implicit flag is reset because otherwise - // a local implicit "lazy val x" will create an ambiguity with itself - // via "x$lzy" as can be seen in test #3927. - private val localLazyVal = isLazy && !owner.isClass - private val nameSuffix = - if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING - else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING - - def derivedName = tree.name.append(nameSuffix) - - def createSym(getter: MethodSymbol) = { - val sym = owner.newValue(derivedName, tree.pos, derivedMods.flags) - if (isLazy) sym setLazyAccessor getter - sym - } + // note: tree.tpt may be EmptyTree, which will be a problem when use as the tpt of a parameter + // the completer will patch this up (we can't do this now without completing the field) + val missingTpt = tree.tpt.isEmpty + val tptToPatch = if (missingTpt) TypeTree() else tree.tpt.duplicate - def derivedSym = tree.symbol + val vparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), setterParam, tptToPatch, EmptyTree)) - def derivedMods = - if (!localLazyVal) tree.mods & FieldFlags | PrivateLocal | (if (isLazy) MUTABLE else 0) - else (tree.mods | ARTIFACT | MUTABLE) & ~IMPLICIT + val tpt = TypeTree(UnitTpe) - // TODO: why is this different from the symbol!? - private def derivedModsForTree = tree.mods | PrivateLocal + val rhs = + if (noFieldFor(tree, owner)) EmptyTree + else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam)) - def derivedTree = - if (Field.noFieldFor(tree)) EmptyTree - else if (isLazy) copyValDef(tree)(mods = derivedModsForTree, name = derivedName, rhs = EmptyTree).setPos(tree.pos.focus) - else copyValDef(tree)(mods = derivedModsForTree, name = derivedName) + newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = List(vparams), tpt = tpt) + } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 6b11dec967..0cd547c1eb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -129,6 +129,7 @@ trait Namers extends MethodSynthesis { !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter !isEnumConstant(vd) // enums can only occur in classes, so only check here + /** Determines whether this field holds an enum constant. * To qualify, the following conditions must be met: * - The field's class has the ENUM flag set @@ -685,6 +686,8 @@ trait Namers extends MethodSynthesis { if (name == nme.copy && sym.isSynthetic) enterCopyMethod(tree) + else if (name == nme.apply && sym.hasAllFlags(SYNTHETIC | CASE)) + sym setInfo caseApplyMethodCompleter(tree, completerOf(tree).asInstanceOf[LockingTypeCompleter]) else sym setInfo completerOf(tree) } @@ -803,86 +806,98 @@ trait Namers extends MethodSynthesis { import AnnotationInfo.{mkFilter => annotationFilter} - def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { sym => - val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter annotationFilter(FieldTargetClass, !tree.mods.isParamAccessor) + def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) - sym setInfo typeSig(tree, annots) + val annotations = classSym.initialize.annotations - validate(sym) + methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def accessorTypeCompleter(tree: ValDef, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - // println(s"triaging for ${sym.debugFlagString} $sym from $valAnnots to $annots") - - // typeSig calls valDefSig (because tree: ValDef) - // sym is an accessor, while tree is the field (which may have the same symbol as the getter, or maybe it's the field) - // TODO: can we make this work? typeSig is called on same tree (valdef) to complete info for field and all its accessors - // reuse work done in valTypeCompleter if we already computed the type signature of the val - // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) -// val valSig = -// if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info -// else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + def caseApplyMethodCompleter(tree: DefDef, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) - val valSig = typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + // don't propagate e.g. @volatile annot to apply's argument + def retainOnlyParamAnnots(param: Symbol) = + param setAnnotations (param.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = false)) - val sig = accessorSigFromFieldTp(sym, isSetter, valSig) + methSym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots)) + } + // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, + // as specified by Field.noFieldFor) + def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { fieldOrGetterSym => val mods = tree.mods - if (mods.annotations.nonEmpty) { - val annotSigs = annotSig(mods.annotations) - - // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis - // (TODO: can we look at symbols earlier?) - if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) - && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) - BeanPropertyAnnotationLimitationError(tree) + val isGetter = fieldOrGetterSym.isMethod + val annots = + if (mods.annotations.isEmpty) Nil + else { + val annotSigs = annotSig(mods.annotations) + if (isGetter) filterAccessorAnnots(annotSigs, tree) // if this is really a getter, retain annots targeting either field/getter + else annotSigs filter annotationFilter(FieldTargetClass, !mods.isParamAccessor) + } - sym setAnnotations (annotSigs filter filterAccessorAnnotations(isSetter)) - } + // must use typeSig, not memberSig (TODO: when do we need to switch namers?) + val sig = typeSig(tree, annots) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig) - validate(sym) + validate(fieldOrGetterSym) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def beanAccessorTypeCompleter(tree: ValDef, missingTpt: Boolean, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - context.unit.synthetics get sym match { + // knowing `isBean`, we could derive `isSetter` from `valDef.name` + def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = mkTypeCompleter(valDef) { accessorSym => + context.unit.synthetics get accessorSym match { case Some(ddef: DefDef) => - // sym is an accessor, while tree is the field (for traits it's actually the getter, and we're completing the setter) + // `accessorSym` is the accessor for which we're completing the info (tree == ddef), + // while `valDef` is the field definition that spawned the accessor + // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol + // reuse work done in valTypeCompleter if we already computed the type signature of the val // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) val valSig = - if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info - else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info + else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig) // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized - if (missingTpt) { // can't look at tree.tpt here because it may have been completed by now + // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`) + // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back... + // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed) + if (missingTpt || accessorSym.isParamAccessor) { if (!isSetter) ddef.tpt setType valSig else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig - else throw new TypeError(tree.pos, s"Internal error: could not complete parameter/return type for $ddef from $sym") + else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym") } + val mods = valDef.mods val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter filterBeanAccessorAnnotations(isSetter) + if (mods.annotations.isEmpty) Nil + else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean) + + // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types) + // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig) + val sig = + if (isSetter || isBean) typeSig(ddef, annots) + else { + if (annots.nonEmpty) annotate(accessorSym, annots) + + NullaryMethodType(valSig) + } - val sig = typeSig(ddef, annots) + accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + if (!isBean && accessorSym.isOverloaded) + if (isSetter) ddef.rhs.setType(ErrorType) + else GetterDefinedTwiceError(accessorSym) - validate(sym) + validate(accessorSym) case _ => - throw new TypeError(tree.pos, s"Internal error: no synthetic tree found for bean accessor $sym") + throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym") } - } - // see scala.annotation.meta's package class for more info // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. // The defaults are: @@ -893,24 +908,33 @@ trait Namers extends MethodSynthesis { // // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit? // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter) - private def filterAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) + private def filterAccessorAnnots(annotSigs: List[global.AnnotationInfo], tree: global.ValDef, isSetter: Boolean = false, isBean: Boolean = false): List[AnnotationInfo] = { + val mods = tree.mods + if (!isBean) { + // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis + // (TODO: can we look at symbols earlier?) + if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) + && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) + BeanPropertyAnnotationLimitationError(tree) + } + + def filterAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) - private def filterBeanAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) + def filterBeanAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) + annotSigs filter (if (isBean) filterBeanAccessorAnnotations else filterAccessorAnnotations) + } - private def accessorSigFromFieldTp(sym: Symbol, isSetter: Boolean, tp: Type): Type = - if (isSetter) MethodType(List(sym.newSyntheticValueParam(tp)), UnitTpe) - else NullaryMethodType(tp) def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => val selftpe = typer.typedType(tree).tpe @@ -959,7 +983,7 @@ trait Namers extends MethodSynthesis { ) dropIllegalStarTypes( if (shouldWiden) tpe.widen - else if (sym.isFinal) tpe // "final val" allowed to retain constant type + else if (sym.isFinal && !sym.isLazy) tpe // "final val" allowed to retain constant type else tpe.deconst ) } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 8449260fe6..8034d056d7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -450,9 +450,9 @@ abstract class RefChecks extends Transform { } else if (other.isStable && !member.isStable) { // (1.4) overrideError("needs to be a stable, immutable value") } else if (member.isValue && member.isLazy && - other.isValue && !other.isSourceMethod && !other.isDeferred && !other.isLazy) { + other.isValue && other.hasFlag(STABLE) && !(other.isDeferred || other.isLazy)) { overrideError("cannot override a concrete non-lazy value") - } else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred && // !(other.hasFlag(MODULE) && other.hasFlag(PACKAGE | JAVA)) && other.hasFlag(LAZY) && (!other.isMethod || other.hasFlag(STABLE)) && !other.hasFlag(DEFERRED) + } else if (other.isValue && other.isLazy && member.isValue && !member.isLazy) { overrideError("must be declared lazy to override a concrete lazy value") } else if (other.isDeferred && member.isTermMacro && member.extendedOverriddenSymbols.forall(_.isDeferred)) { // (1.9) @@ -609,7 +609,7 @@ abstract class RefChecks extends Transform { val (missing, rest) = memberList partition (m => m.isDeferred && !ignoreDeferred(m)) // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. - val grouped = missing groupBy (sym => analyzer.underlyingSymbol(sym).name) + val grouped = missing groupBy (_.name.getterName) val missingMethods = grouped.toList flatMap { case (name, syms) => if (syms exists (_.isSetter)) syms filterNot (_.isGetter) @@ -651,15 +651,16 @@ abstract class RefChecks extends Transform { // Give a specific error message for abstract vars based on why it fails: // It could be unimplemented, have only one accessor, or be uninitialized. - if (underlying.isVariable) { - val isMultiple = grouped.getOrElse(underlying.name, Nil).size > 1 + val groupedAccessors = grouped.getOrElse(member.name.getterName, Nil) + val isMultiple = groupedAccessors.size > 1 + if (groupedAccessors.exists(_.isSetter) || (member.isGetter && !isMultiple && member.setterIn(member.owner).exists)) { // If both getter and setter are missing, squelch the setter error. if (member.isSetter && isMultiple) () else undefined( if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" - else analyzer.abstractVarMessage(member) + else "\n(Note that variables need to be initialized to be defined)" ) } else if (underlying.isMethod) { @@ -919,17 +920,11 @@ abstract class RefChecks extends Transform { var index = -1 for (stat <- stats) { index = index + 1 - def enterSym(sym: Symbol) = if (sym.isLocalToBlock) { - currentLevel.scope.enter(sym) - symIndex(sym) = index - } stat match { - case DefDef(_, _, _, _, _, _) if stat.symbol.isLazy => - enterSym(stat.symbol) - case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) | ValDef(_, _, _, _) => - //assert(stat.symbol != NoSymbol, stat);//debug - enterSym(stat.symbol.lazyAccessorOrSelf) + case _ : MemberDef if stat.symbol.isLocalToBlock => + currentLevel.scope.enter(stat.symbol) + symIndex(stat.symbol) = index case _ => } } @@ -1180,10 +1175,10 @@ abstract class RefChecks extends Transform { val tree1 = transform(tree) // important to do before forward reference check if (tree1.symbol.isLazy) tree1 :: Nil else { - val lazySym = tree.symbol.lazyAccessorOrSelf - if (lazySym.isLocalToBlock && index <= currentLevel.maxindex) { + val sym = tree.symbol + if (sym.isLocalToBlock && index <= currentLevel.maxindex) { debuglog("refsym = " + currentLevel.refsym) - reporter.error(currentLevel.refpos, "forward reference extends over definition of " + lazySym) + reporter.error(currentLevel.refpos, "forward reference extends over definition of " + sym) } tree1 :: Nil } @@ -1451,9 +1446,9 @@ abstract class RefChecks extends Transform { ) } - sym.isSourceMethod && + sym.name == nme.apply && + !(sym hasFlag STABLE) && // ??? sym.isCase && - sym.name == nme.apply && isClassTypeAccessible(tree) && !tree.tpe.finalResultType.typeSymbol.primaryConstructor.isLessAccessibleThan(tree.symbol) } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index bee327c760..b66dbf21c0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -97,7 +97,7 @@ trait TypeDiagnostics { /** An explanatory note to be added to error messages * when there's a problem with abstract var defs */ def abstractVarMessage(sym: Symbol): String = - if (underlyingSymbol(sym).isVariable) + if (sym.isSetter || sym.isGetter && sym.setterIn(sym.owner).exists) "\n(Note that variables need to be initialized to be defined)" else "" @@ -140,7 +140,7 @@ trait TypeDiagnostics { * TODO: is it wise to create new symbols simply to generate error message? is this safe in interactive/resident mode? */ def underlyingSymbol(member: Symbol): Symbol = - if (!member.hasAccessorFlag || member.owner.isTrait) member + if (!member.hasAccessorFlag || member.accessed == NoSymbol) member else if (!member.isDeferred) member.accessed else { val getter = if (member.isSetter) member.getterIn(member.owner) else member diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a95ecd360c..3360599c1b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -128,6 +128,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def canTranslateEmptyListToNil = true def missingSelectErrorTree(tree: Tree, qual: Tree, name: Name): Tree = tree + // used to exempt synthetic accessors (i.e. those that are synthesized by the compiler to access a field) + // from skolemization because there's a weird bug that causes spurious type mismatches + // (it seems to have something to do with existential abstraction over values + // https://github.com/scala/scala-dev/issues/165 + // when we're past typer, lazy accessors are synthetic, but before they are user-defined + // to make this hack less hacky, we could rework our flag assignment to allow for + // requiring both the ACCESSOR and the SYNTHETIC bits to trigger the exemption + private def isSyntheticAccessor(sym: Symbol) = sym.isAccessor && (!sym.isLazy || isPastTyper) + // when type checking during erasure, generate erased types in spots that aren't transformed by erasure // (it erases in TypeTrees, but not in, e.g., the type a Function node) def phasedAppliedType(sym: Symbol, args: List[Type]) = { @@ -1159,7 +1168,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper adapt(tree setType restpe, mode, pt, original) case TypeRef(_, ByNameParamClass, arg :: Nil) if mode.inExprMode => // (2) adapt(tree setType arg, mode, pt, original) - case tp if mode.typingExprNotLhs && isExistentialType(tp) => + case tp if mode.typingExprNotLhs && isExistentialType(tp) && !isSyntheticAccessor(context.owner) => adapt(tree setType tp.dealias.skolemizeExistential(context.owner, tree), mode, pt, original) case PolyType(tparams, restpe) if mode.inNone(TAPPmode | PATTERNmode) && !context.inTypeConstructorAllowed => // (3) // assert((mode & HKmode) == 0) //@M a PolyType in HKmode represents an anonymous type function, @@ -1373,13 +1382,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper notAllowed(s"redefinition of $name method. See SIP-15, criterion 4.") else if (stat.symbol != null && stat.symbol.isParamAccessor) notAllowed("additional parameter") - // concrete accessor (getter) in trait corresponds to a field definition (neg/anytrait.scala) - // TODO: only reject accessors that actually give rise to field (e.g., a constant-type val is fine) - else if (!isValueClass && stat.symbol.isAccessor && !stat.symbol.isDeferred) - notAllowed("field definition") checkEphemeralDeep.traverse(rhs) - // for value class or "exotic" vals in traits - // (traits don't receive ValDefs for regular vals until fields phase -- well, except for early initialized/lazy vals) case _: ValDef => notAllowed("field definition") case _: ModuleDef => @@ -1956,11 +1959,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType]) - val body2 = { - val body2 = - if (isPastTyper || reporter.hasErrors) body1 - else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _)) - val primaryCtor = treeInfo.firstConstructor(body2) + val bodyWithPrimaryCtor = { + val primaryCtor = treeInfo.firstConstructor(body1) val primaryCtor1 = primaryCtor match { case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) => val argss = superArgs(parents1.head) getOrElse Nil @@ -1969,10 +1969,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos case _ => primaryCtor } - body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } + body1 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } } - val body3 = typedStats(body2, templ.symbol) + val body3 = typedStats(bodyWithPrimaryCtor, templ.symbol) if (clazz.info.firstParent.typeSymbol == AnyValClass) validateDerivedValueClass(clazz, body3) @@ -2436,13 +2436,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => } } - val stats1 = if (isPastTyper) block.stats else - block.stats.flatMap { - case vd@ValDef(_, _, _, _) if vd.symbol.isLazy => - namer.addDerivedTrees(Typer.this, vd) - case stat => stat::Nil - } - val stats2 = typedStats(stats1, context.owner, warnPure = false) + val statsTyped = typedStats(block.stats, context.owner, warnPure = false) val expr1 = typed(block.expr, mode &~ (FUNmode | QUALmode), pt) // sanity check block for unintended expr placement @@ -2456,18 +2450,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def checkPure(t: Tree, supple: Boolean): Unit = if (treeInfo.isPureExprForWarningPurposes(t)) { val msg = "a pure expression does nothing in statement position" - val parens = if (stats2.length + count > 1) "multiline expressions might require enclosing parentheses" else "" + val parens = if (statsTyped.length + count > 1) "multiline expressions might require enclosing parentheses" else "" val discard = if (adapted) "; a value can be silently discarded when Unit is expected" else "" val text = if (supple) s"${parens}${discard}" else if (!parens.isEmpty) s"${msg}; ${parens}" else msg context.warning(t.pos, text) } - stats2.foreach(checkPure(_, supple = false)) + statsTyped.foreach(checkPure(_, supple = false)) if (result0.nonEmpty) checkPure(result0, supple = true) } - treeCopy.Block(block, stats2, expr1) + treeCopy.Block(block, statsTyped, expr1) .setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst) } finally { // enable escaping privates checking from the outside and recycle @@ -3171,6 +3165,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case (ClassDef(cmods, cname, _, _), DefDef(dmods, dname, _, _, _, _)) => cmods.isImplicit && dmods.isImplicit && cname.toTermName == dname + // ValDef and Accessor + case (ValDef(_, cname, _, _), DefDef(_, dname, _, _, _, _)) => + cname.getterName == dname.getterName + case _ => false } @@ -4455,8 +4453,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def narrowRhs(tp: Type) = { val sym = context.tree.symbol context.tree match { case ValDef(mods, _, _, Apply(Select(`tree`, _), _)) if !mods.isMutable && sym != null && sym != NoSymbol => - val sym1 = if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) - else sym.lazyAccessorOrSelf + val sym1 = + if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) + else sym val pre = if (sym1.owner.isClass) sym1.owner.thisType else NoPrefix intersectionType(List(tp, singleType(pre, sym1))) case _ => tp diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 22fb0728e6..f2e9b260b0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -128,6 +128,7 @@ trait Unapplies extends ast.TreeDSL { */ def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = { val tparams = constrTparamsInvariant(cdef) + val cparamss = constrParamss(cdef) def classtpe = classType(cdef, tparams) atPos(cdef.pos.focus)( diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 64535a749f..715ba0d4f3 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -72,8 +72,6 @@ trait InteractiveAnalyzer extends Analyzer { override def enterExistingSym(sym: Symbol, tree: Tree): Context = { if (sym != null && sym.owner.isTerm) { enterIfNotThere(sym) - if (sym.isLazy) - sym.lazyAccessor andAlso enterIfNotThere for (defAtt <- sym.attachments.get[DefaultsOfLocalMethodAttachment]) defAtt.defaultGetters foreach enterIfNotThere diff --git a/src/library/scala/runtime/LazyRef.scala b/src/library/scala/runtime/LazyRef.scala new file mode 100644 index 0000000000..3cd2d2d06b --- /dev/null +++ b/src/library/scala/runtime/LazyRef.scala @@ -0,0 +1,52 @@ +package scala.runtime + +/* Classes used as holders for local lazy vals */ + +class LazyRef[T] { + var value: T = _ + @volatile var initialized: Boolean = false +} + +class LazyBoolean { + var value: Boolean = _ + @volatile var initialized: Boolean = false +} + +class LazyByte { + var value: Byte = _ + @volatile var initialized: Boolean = false +} + +class LazyChar { + var value: Char = _ + @volatile var initialized: Boolean = false +} + +class LazyShort { + var value: Short = _ + @volatile var initialized: Boolean = false +} + +class LazyInt { + var value: Int = _ + @volatile var initialized: Boolean = false +} + +class LazyLong { + var value: Long = _ + @volatile var initialized: Boolean = false +} + +class LazyFloat { + var value: Float = _ + @volatile var initialized: Boolean = false +} + +class LazyDouble { + var value: Double = _ + @volatile var initialized: Boolean = false +} + +class LazyUnit { + @volatile var initialized: Boolean = false +} diff --git a/src/manual/scala/man1/scalac.scala b/src/manual/scala/man1/scalac.scala index 6ffcccea25..79c175e0f0 100644 --- a/src/manual/scala/man1/scalac.scala +++ b/src/manual/scala/man1/scalac.scala @@ -379,8 +379,8 @@ object scalac extends Command { MItalic("posterasure"), "clean up erased inline classes"), Definition( - MItalic("lazyvals"), - "allocate bitmaps, translate lazy vals into lazified defs"), + MItalic("fields"), + "synthesize accessors and fields, including bitmaps for lazy vals"), Definition( MItalic("lambdalift"), "move nested functions to top level"), diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index d0539dfd42..89d1b0637a 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -81,7 +81,7 @@ trait Definitions extends api.StandardDefinitions { } } - private[Definitions] def classesMap[T](f: Name => T) = symbolsMap(ScalaValueClassesNoUnit, f) + private[Definitions] def classesMap[T](f: Name => T): Map[Symbol, T] = symbolsMap(ScalaValueClassesNoUnit, f) private def symbolsMap[T](syms: List[Symbol], f: Name => T): Map[Symbol, T] = mapFrom(syms)(x => f(x.name)) private def symbolsMapFilt[T](syms: List[Symbol], p: Name => Boolean, f: Name => T) = symbolsMap(syms filter (x => p(x.name)), f) @@ -93,6 +93,9 @@ trait Definitions extends api.StandardDefinitions { lazy val boxedClass = classesMap(x => getClassByName(boxedName(x))) lazy val refClass = classesMap(x => getRequiredClass("scala.runtime." + x + "Ref")) lazy val volatileRefClass = classesMap(x => getRequiredClass("scala.runtime.Volatile" + x + "Ref")) + lazy val lazyHolders = symbolsMap(ScalaValueClasses, x => getClassIfDefined("scala.runtime.Lazy" + x)) + lazy val LazyRefClass = getClassIfDefined("scala.runtime.LazyRef") + lazy val LazyUnitClass = getClassIfDefined("scala.runtime.LazyUnit") lazy val allRefClasses: Set[Symbol] = { refClass.values.toSet ++ volatileRefClass.values.toSet ++ Set(VolatileObjectRefClass, ObjectRefClass) diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 925018d3a6..c8e4d3d1d5 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -704,6 +704,7 @@ trait StdNames { val immutable: NameType = "immutable" val implicitly: NameType = "implicitly" val in: NameType = "in" + val initialized : NameType = "initialized" val internal: NameType = "internal" val inlinedEquals: NameType = "inlinedEquals" val isArray: NameType = "isArray" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 487aadf5e5..ac025e50ae 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -655,7 +655,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => isClass && isFinal && loop(typeParams) } - final def isLazyAccessor = isLazy && lazyAccessor != NoSymbol final def isOverridableMember = !(isClass || isEffectivelyFinal) && safeOwner.isClass /** Does this symbol denote a wrapper created by the repl? */ @@ -1683,7 +1682,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * * - packageobjects (follows namer) * - superaccessors (follows typer) - * - lazyvals (follows erasure) + * - lambdaLift (follows erasure) * - null */ private def unsafeTypeParamPhase = { @@ -2075,11 +2074,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => */ def alias: Symbol = NoSymbol - /** For a lazy value, its lazy accessor. NoSymbol for all others. */ + @deprecated("No longer applicable, as lazy vals are not desugared until the fields phase", "2.12.0") // used by scala-refactoring def lazyAccessor: Symbol = NoSymbol - /** If this is a lazy value, the lazy accessor; otherwise this symbol. */ - def lazyAccessorOrSelf: Symbol = if (isLazy) lazyAccessor else this + @deprecated("No longer applicable, as lazy vals are not desugared until the fields phase", "2.12.0") + def lazyAccessorOrSelf: Symbol = NoSymbol /** `accessed`, if this is an accessor that should have an underlying field. Otherwise, `this`. * Note that a "regular" accessor in a trait does not have a field, as an interface cannot define a field. @@ -2088,7 +2087,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * as they are an implementation detail that's irrelevant to type checking. */ def accessedOrSelf: Symbol = - if (hasAccessorFlag && (!owner.isTrait || hasFlag(PRESUPER | LAZY))) accessed + if (hasAccessorFlag && (!owner.isTrait || hasFlag(PRESUPER))) accessed else this /** For an outer accessor: The class from which the outer originates. @@ -2834,17 +2833,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => this } - def setLazyAccessor(sym: Symbol): TermSymbol = { - assert(isLazy && (referenced == NoSymbol || referenced == sym), (this, debugFlagString, referenced, sym)) - referenced = sym - this - } - - override def lazyAccessor: Symbol = { - assert(isLazy, this) - referenced - } - /** change name by appending $$<fully-qualified-name-of-class `base`> * Do the same for any accessed symbols or setters/getters */ diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index b9f3e987ee..61937958dd 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -293,6 +293,26 @@ abstract class TreeInfo { } } + + // No field for these vals, which means the ValDef carries the symbol of the getter (and not the field symbol) + // - abstract vals have no value we could store (until they become concrete, potentially) + // - lazy vals: the ValDef carries the symbol of the lazy accessor. + // The sausage factory will spew out the inner workings during the fields phase (actual bitmaps won't follow + // until lazyvals & mixins, though we should move this stuff from mixins to lazyvals now that fields takes care of mixing in lazy vals) + // - concrete vals in traits don't yield a field here either (their getter's RHS has the initial value) + // Constructors will move the assignment to the constructor, abstracting over the field using the field setter, + // and Fields will add a field to the class that mixes in the trait, implementing the accessors in terms of it + // + // The following case does receive a field symbol (until it's eliminated during the fields phase): + // - a concrete val with a statically known value (ConstantType) + // performs its side effect according to lazy/strict semantics, but doesn't need to store its value + // each access will "evaluate" the RHS (a literal) again + // + // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer. + // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does. + def noFieldFor(vd: ValDef, owner: Symbol) = vd.mods.isDeferred || vd.mods.isLazy || (owner.isTrait && !vd.mods.hasFlag(PRESUPER)) + + def isDefaultGetter(tree: Tree) = { tree.symbol != null && tree.symbol.isDefaultGetter } diff --git a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala index 4bc804445c..c6cb0d0223 100644 --- a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala +++ b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala @@ -227,9 +227,7 @@ abstract class UnPickler { return NoSymbol if (tag == EXTMODCLASSref) { - val moduleVar = owner.info.decl(nme.moduleVarName(name.toTermName)) - if (moduleVar.isLazyAccessor) - return moduleVar.lazyAccessor.lazyAccessor + owner.info.decl(nme.moduleVarName(name.toTermName)) } NoSymbol } diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 62ca50d035..e2f1e74740 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -147,21 +147,20 @@ trait Erasure { case AnnotatedType(_, atp) => apply(atp) case ClassInfoType(parents, decls, clazz) => - ClassInfoType( - if (clazz == ObjectClass || isPrimitiveValueClass(clazz) || parents.isEmpty) Nil + val newParents = + if (parents.isEmpty || clazz == ObjectClass || isPrimitiveValueClass(clazz)) Nil else if (clazz == ArrayClass) ObjectTpe :: Nil else { - val erasedParents = parents map this + val erasedParents = parents mapConserve this // drop first parent for traits -- it has been normalized to a class by now, // but we should drop that in bytecode - val firstParent = - if (clazz.hasFlag(Flags.TRAIT) && !clazz.hasFlag(Flags.JAVA)) ObjectTpe - else erasedParents.head - - firstParent :: erasedParents.tail.filter(_.typeSymbol != ObjectClass) - }, - decls, clazz) + if (clazz.hasFlag(Flags.TRAIT) && !clazz.hasFlag(Flags.JAVA)) + ObjectTpe :: erasedParents.tail.filter(_.typeSymbol != ObjectClass) + else erasedParents + } + if (newParents eq parents) tp + else ClassInfoType(newParents, decls, clazz) case _ => mapOver(tp) } @@ -343,23 +342,30 @@ trait Erasure { } } - /** The symbol's erased info. This is the type's erasure, except for the following symbols: - * - * - For $asInstanceOf : [T]T - * - For $isInstanceOf : [T]scala#Boolean - * - For class Array : [T]C where C is the erased classinfo of the Array class. - * - For Array[T].<init> : {scala#Int)Array[T] - * - For a type parameter : A type bounds type consisting of the erasures of its bounds. - */ + /** The symbol's erased info. This is the type's erasure, except for the following primitive symbols: + * + * - $asInstanceOf --> [T]T + * - $isInstanceOf --> [T]scala#Boolean + * - synchronized --> [T](x: T)T + * - class Array --> [T]C where C is the erased classinfo of the Array class. + * - Array[T].<init> --> {scala#Int)Array[T] + * + * An abstract type's info erases to a TypeBounds type consisting of the erasures of the abstract type's bounds. + */ def transformInfo(sym: Symbol, tp: Type): Type = { - if (sym == Object_asInstanceOf) + // Do not erase the primitive `synchronized` method's info or the info of its parameter. + // We do erase the info of its type param so that subtyping can relate its bounds after erasure. + def synchronizedPrimitive(sym: Symbol) = + sym == Object_synchronized || (sym.owner == Object_synchronized && sym.isTerm) + + if (sym == Object_asInstanceOf || synchronizedPrimitive(sym)) sym.info else if (sym == Object_isInstanceOf || sym == ArrayClass) PolyType(sym.info.typeParams, specialErasure(sym)(sym.info.resultType)) else if (sym.isAbstractType) - TypeBounds(WildcardType, WildcardType) + TypeBounds(WildcardType, WildcardType) // TODO why not use the erasure of the type's bounds, as stated in the doc? else if (sym.isTerm && sym.owner == ArrayClass) { - if (sym.isClassConstructor) + if (sym.isClassConstructor) // TODO: switch on name for all branches -- this one is sym.name == nme.CONSTRUCTOR tp match { case MethodType(params, TypeRef(pre, sym1, args)) => MethodType(cloneSymbolsAndModify(params, specialErasure(sym)), @@ -376,12 +382,14 @@ trait Erasure { } else if ( sym.owner != NoSymbol && sym.owner.owner == ArrayClass && - sym == Array_update.paramss.head(1)) { + sym == Array_update.paramss.head(1)) { // TODO: can we simplify the guard, perhaps cache the symbol to compare to? // special case for Array.update: the non-erased type remains, i.e. (Int,A)Unit // since the erasure type map gets applied to every symbol, we have to catch the // symbol here tp } else { + // TODO OPT: altogether, there are 9 symbols that we special-case. + // Could we get to the common case more quickly by looking them up in a set? specialErasure(sym)(tp) } } diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index f55b33959a..d56cecc965 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -432,6 +432,9 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.boxedClass definitions.refClass definitions.volatileRefClass + definitions.lazyHolders + definitions.LazyRefClass + definitions.LazyUnitClass definitions.allRefClasses definitions.UnitClass definitions.ByteClass diff --git a/src/repl/scala/tools/nsc/interpreter/Phased.scala b/src/repl/scala/tools/nsc/interpreter/Phased.scala index d1d422ce3e..dd327a13d4 100644 --- a/src/repl/scala/tools/nsc/interpreter/Phased.scala +++ b/src/repl/scala/tools/nsc/interpreter/Phased.scala @@ -88,7 +88,7 @@ trait Phased { lazy val all = List( Parser, Namer, Packageobjects, Typer, Superaccessors, Pickler, Refchecks, - Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Lazyvals, Lambdalift, + Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Fields, Lambdalift, Constructors, Flatten, Mixin, Cleanup, Delambdafy, Jvm, Terminal ) lazy val nameMap = all.map(x => x.name -> x).toMap withDefaultValue NoPhaseName @@ -114,12 +114,12 @@ trait Phased { case object Pickler extends PhaseName case object Refchecks extends PhaseName case object Uncurry extends PhaseName + case object Fields extends PhaseName case object Tailcalls extends PhaseName case object Specialize extends PhaseName case object Explicitouter extends PhaseName case object Erasure extends PhaseName case object PostErasure extends PhaseName - case object Lazyvals extends PhaseName case object Lambdalift extends PhaseName case object Constructors extends PhaseName case object Flatten extends PhaseName diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala index 928cb34d30..fb9a5ce7eb 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala @@ -106,10 +106,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // in the doc comment of MyClass def linkTarget: DocTemplateImpl = inTpl - lazy val comment = { - val documented = if (sym.hasAccessorFlag) sym.accessed else sym - thisFactory.comment(documented, linkTarget, inTpl) - } + // if there is a field symbol, the ValDef will use it, which means docs attached to it will be under the field symbol, not the getter's + protected[this] def commentCarryingSymbol(sym: Symbol) = + if (sym.hasAccessorFlag && sym.accessed.exists) sym.accessed else sym + + lazy val comment = thisFactory.comment(commentCarryingSymbol(sym), linkTarget, inTpl) + def group = comment flatMap (_.group) getOrElse defaultGroup override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot @@ -476,17 +478,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override lazy val comment = { def nonRootTemplate(sym: Symbol): Option[DocTemplateImpl] = if (sym eq RootPackage) None else findTemplateMaybe(sym) + /* Variable precedence order for implicitly added members: Take the variable definitions from ... * 1. the target of the implicit conversion * 2. the definition template (owner) * 3. the current template */ - val inRealTpl = conversion.flatMap { conv => - nonRootTemplate(conv.toType.typeSymbol) - } orElse nonRootTemplate(sym.owner) orElse Option(inTpl) - inRealTpl flatMap { tpl => - thisFactory.comment(sym, tpl, tpl) - } + val inRealTpl = ( + conversion.flatMap(conv => nonRootTemplate(conv.toType.typeSymbol)) + orElse nonRootTemplate(sym.owner) + orElse Option(inTpl)) + + inRealTpl flatMap (tpl => thisFactory.comment(commentCarryingSymbol(sym), tpl, tpl)) } override def inDefinitionTemplates = useCaseOf.fold(super.inDefinitionTemplates)(_.inDefinitionTemplates) |