summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan@lightbend.com>2016-09-01 10:41:28 +0200
committerGitHub <noreply@github.com>2016-09-01 10:41:28 +0200
commitf90bf565a529d0e95155992a3c243952183f25a5 (patch)
tree151f8d8d79dba64a96d12b1c4a9c4fa38269dcf8 /src
parent45edf8699f76faa5d1b035fb8f887621bcbb9934 (diff)
parent743f0d2c2a33bc8ab0c1f7bee796865672ba2fcc (diff)
downloadscala-f90bf565a529d0e95155992a3c243952183f25a5.tar.gz
scala-f90bf565a529d0e95155992a3c243952183f25a5.tar.bz2
scala-f90bf565a529d0e95155992a3c243952183f25a5.zip
Merge pull request #5294 from adriaanm/fields-lazies
Fields: expand lazy vals during fields, like modules
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/reflect/reify/phases/Reshape.scala36
-rw-r--r--src/compiler/scala/tools/ant/Scalac.scala2
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala12
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala22
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala46
-rw-r--r--src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala465
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala5
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala19
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala5
-rw-r--r--src/compiler/scala/tools/nsc/transform/Fields.scala397
-rw-r--r--src/compiler/scala/tools/nsc/transform/LazyVals.scala318
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala617
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala11
-rw-r--r--src/compiler/scala/tools/nsc/transform/TailCalls.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Duplicators.scala8
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala298
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Namers.scala152
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/RefChecks.scala35
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala4
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala51
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Unapplies.scala1
-rw-r--r--src/interactive/scala/tools/nsc/interactive/Global.scala2
-rw-r--r--src/library/scala/runtime/LazyRef.scala52
-rw-r--r--src/manual/scala/man1/scalac.scala4
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala5
-rw-r--r--src/reflect/scala/reflect/internal/StdNames.scala1
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala22
-rw-r--r--src/reflect/scala/reflect/internal/TreeInfo.scala20
-rw-r--r--src/reflect/scala/reflect/internal/pickling/UnPickler.scala4
-rw-r--r--src/reflect/scala/reflect/internal/transform/Erasure.scala52
-rw-r--r--src/reflect/scala/reflect/runtime/JavaUniverseForce.scala3
-rw-r--r--src/repl/scala/tools/nsc/interpreter/Phased.scala4
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala23
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)