From 7eb34a61ee1e08e95b983fe457307c24cfd76d3a Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 12 Aug 2016 15:18:50 -0700 Subject: git ignore local.sbt my local.sbt: ``` resolvers += "pr" at "https://scala-ci.typesafe.com/artifactory/scala-pr-validation-snapshots/" publishArtifact in (Compile, packageDoc) in ThisBuild := false baseVersionSuffix := s"local-${Process("tools/get-scala-commit-sha").lines.head.substring(0, 7)}" antStyle := true ``` To bootstrap the compiler, assuming your git abbreviates shas to 7 long: ``` function strap () { sbt clean publishLocal sbt -Dstarr.version=2.12.0-local-$(g rev-parse --short HEAD) } ``` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0fff976e90..ea71d02d0b 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ /project/project/target/ /project/project/project/target/ /build-sbt/ +local.sbt -- cgit v1.2.3 From a3604707303e4b1f45b6afabccaf00510b281912 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 9 Aug 2016 22:55:57 -0700 Subject: asmutils decompile classfiles in parallel to aid in diffing bytecode between quick & strap ``` mkdir class-repo cd class-repo git init . ( cd .. ; sbt publishLocal ) v="2.12.0-local-$(g rev-parse --short HEAD)" ( cd ~/git/scala-2 ; sbt -Dstarr.version=$v compile ) for i in compiler interactive junit library partest-extras partest-javaagent reflect repl repl-jline repl-jline-embedded scaladoc scalap do cp -a ~/git/scala/build/quick/classes/$i . ~/git/scala/build/quick/bin/scala scala.tools.nsc.backend.jvm.AsmUtils $(find $i -name "*.class" ) g add $(find $i -name "*.asm" ) done g commit -m"quick" for i in compiler interactive junit library partest-extras partest-javaagent reflect repl repl-jline repl-jline-embedded scaladoc scalap do cp -a ~/git/scala-2/build/quick/classes/$i/* $i/ ~/git/scala/build/quick/bin/scala scala.tools.nsc.backend.jvm.AsmUtils $(find $i -name "*.class" ) done git --no-pager diff | mate ``` --- .../scala/tools/nsc/backend/jvm/AsmUtils.scala | 46 ++++++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) 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() } /** -- cgit v1.2.3 From df3689139c4d4bcd2933364d13b8195c3433eb43 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 14 Jul 2016 16:52:20 -0700 Subject: Fields phase expands lazy vals like modules They remain ValDefs until then. - remove lazy accessor logic now that we have a single ValDef for lazy vals, with the underlying machinery being hidden until the fields phase leave a `@deprecated def lazyAccessor` for scala-refactoring - don't skolemize in purely synthetic getters, but *do* skolemize in lazy accessor during typers Lazy accessors have arbitrary user code, so have to skolemize. We exempt the purely synthetic accessors (`isSyntheticAccessor`) for strict vals, and lazy accessors emitted by the fields phase to avoid spurious type mismatches due to issues with existentials (That bug is tracked as 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 usage to allow for requiring both the ACCESSOR and the SYNTHETIC bits to identify synthetic accessors and trigger the exemption. see also https://github.com/scala/scala-dev/issues/165 ok 7 - pos/existentials-harmful.scala ok 8 - pos/t2435.scala ok 9 - pos/existentials.scala previous attempt: skolemize type of val inside the private[this] val because its type is only observed from inside the accessor methods (inside the method scope its existentials are skolemized) - bean accessors have regular method types, not nullary method types - must re-infer type for param accessor some weirdness with scoping of param accessor vals and defs? - tailcalls detect lazy vals, which are defdefs after fields - can inline constant lazy val from trait - don't mix in fields etc for an overridden lazy val - need try-lift in lazy vals: the assign is not seen in uncurry because fields does the transform (see run/t2333.scala) - ensure field members end up final in bytecode - implicit class companion method: annot filter in completer - update check: previous error message was tangled up with unrelated field definitions (`var s` and `val s_scope`), now it behaves consistently whether those are val/vars or defs - analyzer plugin check update seems benign, but no way to know... - error message gen: there is no underlying symbol for a deferred var look for missing getter/setter instead - avoid retypechecking valdefs while duplicating for specialize see pos/spec-private - Scaladoc uniformly looks to field/accessor symbol - test updates to innerClassAttribute by Lukas --- .../scala/reflect/reify/phases/Reshape.scala | 36 +-- .../scala/tools/nsc/transform/Fields.scala | 138 ++++++++-- src/compiler/scala/tools/nsc/transform/Mixin.scala | 47 +--- .../scala/tools/nsc/transform/TailCalls.scala | 6 +- .../scala/tools/nsc/transform/UnCurry.scala | 4 +- .../scala/tools/nsc/typechecker/Duplicators.scala | 8 +- .../tools/nsc/typechecker/MethodSynthesis.scala | 298 ++++++--------------- .../scala/tools/nsc/typechecker/Namers.scala | 149 ++++++----- .../scala/tools/nsc/typechecker/RefChecks.scala | 35 ++- .../tools/nsc/typechecker/TypeDiagnostics.scala | 4 +- .../scala/tools/nsc/typechecker/Typers.scala | 51 ++-- .../scala/tools/nsc/interactive/Global.scala | 2 - src/reflect/scala/reflect/internal/Symbols.scala | 20 +- src/reflect/scala/reflect/internal/TreeInfo.scala | 20 ++ .../reflect/internal/pickling/UnPickler.scala | 4 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 23 +- test/files/jvm/innerClassAttribute/Test.scala | 12 +- test/files/jvm/javaReflection.check | 8 +- test/files/neg/anytrait.check | 5 +- test/files/neg/names-defaults-neg.check | 8 +- test/files/neg/protected-constructors.check | 18 +- test/files/neg/sabin2.check | 2 +- test/files/neg/t1838.check | 6 +- test/files/neg/t4158.check | 6 +- test/files/neg/t6829.check | 12 +- test/files/neg/t712.check | 3 +- .../files/neg/t8217-local-alias-requires-rhs.check | 6 +- test/files/neg/t963.check | 10 +- test/files/pos/t5240.scala | 8 - test/files/presentation/t5708.check | 2 +- test/files/presentation/t8459.check | 1 + test/files/run/analyzerPlugins.check | 49 ++-- test/files/run/compiler-asSeenFrom.check | 20 +- test/files/run/existential-rangepos.check | 2 +- test/files/run/idempotency-lazy-vals.check | 14 +- test/files/run/lazy-locals.check | 3 - test/files/run/showraw_mods.check | 2 +- test/files/run/t6023.check | 4 +- test/files/run/t6733.check | 2 - test/files/run/trait-fields-override-lazy.check | 2 + test/files/run/trait-fields-override-lazy.scala | 13 + .../scalacheck/quasiquotes/TypecheckedProps.scala | 2 +- .../scala/reflect/internal/PrintersTest.scala | 25 +- .../nsc/backend/jvm/opt/InlineWarningTest.scala | 2 +- .../tools/nsc/backend/jvm/opt/InlinerTest.scala | 8 +- test/scaladoc/run/t7767.scala | 42 ++- 46 files changed, 535 insertions(+), 607 deletions(-) create mode 100644 test/files/run/trait-fields-override-lazy.check create mode 100644 test/files/run/trait-fields-override-lazy.scala 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/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 26e517743a..6339f0002d 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -176,9 +176,9 @@ 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 def newModuleVarSymbol(site: Symbol, module: Symbol, tp: Type, extraFlags: Long): TermSymbol = { + private def newModuleVarSymbol(owner: Symbol, module: Symbol, tp: Type, extraFlags: Long): 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 + val moduleVar = owner.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | extraFlags) setInfo tp addAnnotation VolatileAttr moduleVarOf(module) = moduleVar moduleVar @@ -191,6 +191,37 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } + private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0, localLazyVal: Boolean = false): TermSymbol = { + val flags = member.flags | extraFlags + val name = member.name.toTermName + val pos = member.pos + + // 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. + val nameSuffix = + if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING + else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING + + // TODO: should end up final in bytecode + val fieldFlags = + if (!localLazyVal) flags & FieldFlags | PrivateLocal | MUTABLE + else (flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) + +// println(s"new lazy var sym in $owner for $member ${symtab.Flags.flagsToString(fieldFlags)}") + val sym = owner.newValue(name.append(nameSuffix), pos.focus, fieldFlags | extraFlags) setInfo tp + moduleVarOf(member) = sym + sym + } + + private def lazyValInit(member: Symbol, rhs: Tree) = { + val lazyVar = moduleVarOf(member) + assert(lazyVar.isMutable, lazyVar) + gen.mkAssignAndReturn(lazyVar, rhs) + } + private object synthFieldsAndAccessors extends TypeMap { private def newTraitSetter(getter: Symbol, clazz: Symbol) = { // Add setter for an immutable, memoizing getter @@ -221,7 +252,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 +263,18 @@ 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 + } + + // make sure they end up final in bytecode + final private val fieldFlags = PrivateLocal | FINAL | SYNTHETIC | NEEDS_TREES + 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,7 +288,7 @@ 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 @@ -265,7 +307,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (accessorUnderConsideration && fieldMemoization.stored) { 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,27 +338,36 @@ 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, fieldFlags) + + def newLazyVarMember(member: Symbol): TermSymbol = + newLazyVarSymbol(clazz, member, site.memberType(member).resultType, fieldFlags) // 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 && !(m.info.isInstanceOf[ConstantType] || isUnitType(m.info))) // no need for ASF since we're in the defining class + ) // expand module def in class/object (if they need it -- see modulesNeedingExpansion above) - val expandedModules = - modulesNeedingExpansion map { module => + val expandedModulesAndLazyVals = + modulesAndLazyValsNeedingExpansion map { member => + if (member.isLazy) { + newLazyVarMember(member) + } // expanding module def (top-level or nested in static module) - if (module.isStatic) { // implies m.isOverridingSymbol as per above filter + else 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) } } @@ -344,13 +395,9 @@ 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() @@ -362,6 +409,15 @@ 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 hasFlag MODULE) { + val moduleVar = newModuleVarMember(member) + List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) + } + else if (member hasFlag LAZY) { + val mixedinLazy = cloneAccessor() + val lazyVar = newLazyVarMember(mixedinLazy) + List(lazyVar, newSuperLazy(mixedinLazy, site, lazyVar)) + } else if (member.isGetter && fieldMemoizationIn(member, clazz).stored) { // add field if needed val field = clazz.newValue(member.localName, member.pos) setInfo fieldTypeForGetterIn(member, clazz.thisType) @@ -382,15 +438,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && !fieldMemoizationIn(sym, clazz).stored val newDecls = - if (expandedModules.isEmpty && mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) + if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty) 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 newDecls @@ -421,6 +477,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor def mkField(sym: Symbol) = localTyper.typedPos(sym.pos)(ValDef(sym)).asInstanceOf[ValDef] + // 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] = { @@ -462,8 +519,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else moduleInit(module) ) + def superLazy(getter: Symbol): Some[Tree] = { + 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? + Some(gen.mkAssignAndReturn(moduleVarOf(getter), Apply(Select(Super(This(clazz), tpnme.EMPTY), getter.name), Nil))) + } + 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) map mkAccessor(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) @@ -474,7 +538,6 @@ 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 @@ -502,7 +565,31 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor && (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? + deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) + + /** Normalize ValDefs to corresponding accessors + field + * + * ValDef in trait --> getter DefDef + * Lazy val receives field with a new symbol (if stored) and the ValDef's symbol is moved to a DefDef (the lazy accessor): + * - 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 = ; z } where z can be an identifier or a field. + */ + case vd@ValDef(mods, name, tpt, rhs) if vd.symbol.hasFlag(ACCESSOR) && treeInfo.noFieldFor(vd, clazz) => + def notStored = {val resultType = statSym.info.resultType ; (resultType.isInstanceOf[ConstantType] || isUnitType(resultType))} + val transformedRhs = atOwner(statSym)(transform(rhs)) + + if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) + else if (clazz.isTrait || notStored) mkAccessor(statSym)(transformedRhs) + else if (clazz.isClass) mkAccessor(statSym)(gen.mkAssignAndReturn(moduleVarOf(vd.symbol), transformedRhs)) + else { + // local lazy val (same story as modules: info transformer doesn't get here, so can't drive tree synthesis) + val lazyVar = newLazyVarSymbol(currentOwner, statSym, statSym.info.resultType, extraFlags = 0, localLazyVal = true) + val lazyValInit = gen.mkAssignAndReturn(lazyVar, transformedRhs) + Thicket(mkField(lazyVar) :: mkAccessor(statSym)(lazyValInit) :: Nil) + } // 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) @@ -524,6 +611,7 @@ 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) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index f781426f1a..4be611b747 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -11,6 +11,7 @@ import Flags._ import scala.annotation.tailrec import scala.collection.mutable +// TODO: move lazy vals bitmap creation to lazy vals phase now that lazy vals are mixed in during fields abstract class Mixin extends InfoTransform with ast.TreeDSL { import global._ import definitions._ @@ -57,8 +58,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 ----------------------------------------------- @@ -295,9 +294,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 +316,14 @@ 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) 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 +337,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) ) @@ -499,7 +482,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else if (needsInitFlag(field) && !field.isDeferred) false else return NO_NAME ) - if (field.accessed hasAnnotation TransientAttr) { + if (field.accessedOrSelf hasAnnotation TransientAttr) { if (isNormal) BITMAP_TRANSIENT else BITMAP_CHECKINIT_TRANSIENT } else { @@ -862,17 +845,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { 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 { + val readValue = { assert(getter.hasFlag(PARAMACCESSOR)) fieldAccess(getter) } @@ -902,7 +875,7 @@ 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 lazy/param?!?") // add accessor definitions addDefDef(sym, if (sym.isSetter) setterBody(sym) else getterBody(sym)) @@ -919,7 +892,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (clazz.isTrait) stats1 = stats1.filter { case vd: ValDef => - assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR | LAZY), s"unexpected valdef $vd in trait $clazz") + assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz") false case _ => true } 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 = ; 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 99c1b6991e..033b6cc0cd 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 @@ -803,86 +804,89 @@ 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 - - val valSig = typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig - - val sig = accessorSigFromFieldTp(sym, isSetter, valSig) + // 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) - val sig = typeSig(ddef, annots) + // 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) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + NullaryMethodType(valSig) + } + + accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) - validate(sym) + if (!isBean && accessorSym.isOverloaded) + if (isSetter) ddef.rhs.setType(ErrorType) + else GetterDefinedTwiceError(accessorSym) + + 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 +897,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 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)) - - - private def accessorSigFromFieldTp(sym: Symbol, isSetter: Boolean, tp: Type): Type = - if (isSetter) MethodType(List(sym.newSyntheticValueParam(tp)), UnitTpe) - else NullaryMethodType(tp) + 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)) + + 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) + } + def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => val selftpe = typer.typedType(tree).tpe 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/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/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 487aadf5e5..10ae68cdd1 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? */ @@ -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 $$ * 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/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) diff --git a/test/files/jvm/innerClassAttribute/Test.scala b/test/files/jvm/innerClassAttribute/Test.scala index 5c666a615f..288c6ee30f 100644 --- a/test/files/jvm/innerClassAttribute/Test.scala +++ b/test/files/jvm/innerClassAttribute/Test.scala @@ -298,7 +298,7 @@ object Test extends BytecodeTest { assertEnclosingMethod ("SI_9105$B$5" , "SI_9105", "m$1", "()Ljava/lang/Object;") assertEnclosingMethod ("SI_9105$C$1" , "SI_9105", null , null) assertEnclosingMethod ("SI_9105$D$1" , "SI_9105", "met", "()Lscala/Function1;") - assertEnclosingMethod ("SI_9105$E$1" , "SI_9105", "m$3", "()Ljava/lang/Object;") + assertEnclosingMethod ("SI_9105$E$1" , "SI_9105", "m$2", "()Ljava/lang/Object;") assertEnclosingMethod ("SI_9105$F$1" , "SI_9105", "met", "()Lscala/Function1;") assertNoEnclosingMethod("SI_9105") @@ -311,7 +311,7 @@ object Test extends BytecodeTest { // by-name assertEnclosingMethod("SI_9105$G$1", "SI_9105", null , null) - assertEnclosingMethod("SI_9105$H$1", "SI_9105", "m$2", "()Ljava/lang/Object;") + assertEnclosingMethod("SI_9105$H$1", "SI_9105", "m$3", "()Ljava/lang/Object;") assertEnclosingMethod("SI_9105$I$1", "SI_9105", null , null) assertEnclosingMethod("SI_9105$J$1", "SI_9105", "bnM", "()I") assertEnclosingMethod("SI_9105$K$2", "SI_9105", "m$4", "()Ljava/lang/Object;") @@ -323,11 +323,11 @@ object Test extends BytecodeTest { def testSI_9124() { val classes: Map[String, String] = { List("SI_9124$$anon$10", - "SI_9124$$anon$11", "SI_9124$$anon$12", + "SI_9124$$anon$13", "SI_9124$$anon$8", "SI_9124$$anon$9", - "SI_9124$O$$anon$13").map({ name => + "SI_9124$O$$anon$11").map({ name => val node = loadClassNode(name) val fMethod = node.methods.asScala.find(_.name.startsWith("f")).get.name (fMethod, node.name) @@ -380,8 +380,8 @@ object Test extends BytecodeTest { val b3 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B3$1", "B3$1", flags = publicAbstractInterface) val b4 = assertLocal(_ : InnerClassNode, "ImplClassesAreTopLevel$B4$1", "B4$1", flags = publicAbstractInterface) - testInner("ImplClassesAreTopLevel$$anon$14", an14, b3) - testInner("ImplClassesAreTopLevel$$anon$15", an15, b2) + testInner("ImplClassesAreTopLevel$$anon$14", an14, b2) + testInner("ImplClassesAreTopLevel$$anon$15", an15, b3) testInner("ImplClassesAreTopLevel$$anon$16", an16, b4) testInner("ImplClassesAreTopLevel$B1", b1) diff --git a/test/files/jvm/javaReflection.check b/test/files/jvm/javaReflection.check index 9e9fe36d14..f3924940e9 100644 --- a/test/files/jvm/javaReflection.check +++ b/test/files/jvm/javaReflection.check @@ -2,13 +2,13 @@ A / A (canon) / A (simple) - declared cls: List(class A$B, interface A$C, class A$D$) - enclosing : null (declaring cls) / null (cls) / null (constr) / null (meth) - properties : false (local) / false (member) -A$$anon$1 / null (canon) / $anon$1 (simple) +A$$anon$2 / null (canon) / $anon$2 (simple) - declared cls: List() - enclosing : null (declaring cls) / class A (cls) / null (constr) / null (meth) - properties : true (local) / false (member) A$$anon$3 / null (canon) / $anon$3 (simple) - declared cls: List() -- enclosing : null (declaring cls) / class A (cls) / null (constr) / null (meth) +- enclosing : null (declaring cls) / class A (cls) / null (constr) / public java.lang.Object A.f() (meth) - properties : true (local) / false (member) A$$anon$4 / null (canon) / $anon$4 (simple) - declared cls: List() @@ -16,7 +16,7 @@ A$$anon$4 / null (canon) / $anon$4 (simple) - properties : true (local) / false (member) A$$anon$5 / null (canon) / $anon$5 (simple) - declared cls: List() -- enclosing : null (declaring cls) / class A (cls) / null (constr) / public java.lang.Object A.f() (meth) +- enclosing : null (declaring cls) / class A (cls) / null (constr) / null (meth) - properties : true (local) / false (member) A$$anon$6 / null (canon) / $anon$6 (simple) - declared cls: List() @@ -38,7 +38,7 @@ A$D$ / A.D$ (canon) / D$ (simple) - declared cls: List(class A$D$B, interface A$D$C, class A$D$D$) - enclosing : class A (declaring cls) / class A (cls) / null (constr) / null (meth) - properties : false (local) / true (member) -A$D$$anon$2 / null (canon) / anon$2 (simple) +A$D$$anon$1 / null (canon) / anon$1 (simple) - declared cls: List() - enclosing : null (declaring cls) / class A$D$ (cls) / null (constr) / null (meth) - properties : true (local) / false (member) diff --git a/test/files/neg/anytrait.check b/test/files/neg/anytrait.check index fabe74d379..6d9d681d60 100644 --- a/test/files/neg/anytrait.check +++ b/test/files/neg/anytrait.check @@ -4,4 +4,7 @@ anytrait.scala:3: error: field definition is not allowed in universal trait exte anytrait.scala:5: error: this statement is not allowed in universal trait extending from class Any { x += 1 } ^ -two errors found +anytrait.scala:9: error: field definition is not allowed in universal trait extending from class Any + val y: T + ^ +three errors found diff --git a/test/files/neg/names-defaults-neg.check b/test/files/neg/names-defaults-neg.check index 0a7b1a7157..af164d90ea 100644 --- a/test/files/neg/names-defaults-neg.check +++ b/test/files/neg/names-defaults-neg.check @@ -1,7 +1,3 @@ -names-defaults-neg.scala:65: error: not enough arguments for method apply: (a: Int, b: String)(c: Int*)Fact in object Fact. -Unspecified value parameter b. - val fac = Fact(1)(2, 3) - ^ names-defaults-neg.scala:5: error: type mismatch; found : String("#") required: Int @@ -81,6 +77,10 @@ and method f in object t8 of type (a: Int, b: Object)String match argument types (a: Int,b: String) and expected result type Any println(t8.f(a = 0, b = "1")) // ambiguous reference ^ +names-defaults-neg.scala:65: error: not enough arguments for method apply: (a: Int, b: String)(c: Int*)Fact in object Fact. +Unspecified value parameter b. + val fac = Fact(1)(2, 3) + ^ names-defaults-neg.scala:69: error: wrong number of arguments for pattern A1(x: Int,y: String) A1() match { case A1(_) => () } ^ diff --git a/test/files/neg/protected-constructors.check b/test/files/neg/protected-constructors.check index 0279f5815d..4f076ec993 100644 --- a/test/files/neg/protected-constructors.check +++ b/test/files/neg/protected-constructors.check @@ -1,3 +1,12 @@ +protected-constructors.scala:15: error: class Foo3 in object Ding cannot be accessed in object dingus.Ding + Access to protected class Foo3 not permitted because + enclosing object P in package hungus is not a subclass of + object Ding in package dingus where target is defined + class Bar3 extends Ding.Foo3("abc") + ^ +protected-constructors.scala:15: error: no arguments allowed for nullary constructor Object: ()Object + class Bar3 extends Ding.Foo3("abc") + ^ protected-constructors.scala:17: error: no arguments allowed for nullary constructor Foo1: ()dingus.Foo1 val foo1 = new Foo1("abc") ^ @@ -13,13 +22,4 @@ protected-constructors.scala:19: error: class Foo3 in object Ding cannot be acce object Ding in package dingus where target is defined val foo3 = new Ding.Foo3("abc") ^ -protected-constructors.scala:15: error: class Foo3 in object Ding cannot be accessed in object dingus.Ding - Access to protected class Foo3 not permitted because - enclosing object P in package hungus is not a subclass of - object Ding in package dingus where target is defined - class Bar3 extends Ding.Foo3("abc") - ^ -protected-constructors.scala:15: error: no arguments allowed for nullary constructor Object: ()Object - class Bar3 extends Ding.Foo3("abc") - ^ 5 errors found diff --git a/test/files/neg/sabin2.check b/test/files/neg/sabin2.check index 8a09361069..aa0e8f734c 100644 --- a/test/files/neg/sabin2.check +++ b/test/files/neg/sabin2.check @@ -1,6 +1,6 @@ sabin2.scala:22: error: type mismatch; found : Test.Base#T - required: _7.T where val _7: Test.Base + required: _5.T where val _5: Test.Base a.set(b.get()) // Error ^ one error found diff --git a/test/files/neg/t1838.check b/test/files/neg/t1838.check index a476158c7b..af811a3810 100644 --- a/test/files/neg/t1838.check +++ b/test/files/neg/t1838.check @@ -1,7 +1,7 @@ -t1838.scala:6: error: `sealed' modifier can be used only for classes - sealed val v = 0 - ^ t1838.scala:5: error: `sealed' modifier can be used only for classes sealed def f = 0 ^ +t1838.scala:6: error: `sealed' modifier can be used only for classes + sealed val v = 0 + ^ two errors found diff --git a/test/files/neg/t4158.check b/test/files/neg/t4158.check index af281c52cd..7bac6558f7 100644 --- a/test/files/neg/t4158.check +++ b/test/files/neg/t4158.check @@ -1,7 +1,7 @@ -t4158.scala:3: error: an expression of type Null is ineligible for implicit conversion - var y = null: Int - ^ t4158.scala:2: error: an expression of type Null is ineligible for implicit conversion var x: Int = null ^ +t4158.scala:3: error: an expression of type Null is ineligible for implicit conversion + var y = null: Int + ^ two errors found diff --git a/test/files/neg/t6829.check b/test/files/neg/t6829.check index 914a1c9260..274094f791 100644 --- a/test/files/neg/t6829.check +++ b/test/files/neg/t6829.check @@ -17,32 +17,32 @@ t6829.scala:49: error: not found: value nextState ^ t6829.scala:50: error: type mismatch; found : s.type (with underlying type Any) - required: _53.State where val _53: G + required: _30.State where val _30: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:50: error: type mismatch; found : a.type (with underlying type Any) - required: _53.Action where val _53: G + required: _30.Action where val _30: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:50: error: type mismatch; found : s2.type (with underlying type Any) - required: _53.State where val _53: G + required: _30.State where val _30: G val r = rewards(agent).r(s,a,s2) ^ t6829.scala:51: error: type mismatch; found : s.type (with underlying type Any) - required: _50.State + required: _25.State agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:51: error: type mismatch; found : a.type (with underlying type Any) - required: _50.Action + required: _25.Action agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:51: error: type mismatch; found : s2.type (with underlying type Any) - required: _50.State + required: _25.State agent.learn(s,a,s2,r): G#Agent ^ t6829.scala:53: error: not found: value nextState diff --git a/test/files/neg/t712.check b/test/files/neg/t712.check index 831e943063..3f02b4b294 100644 --- a/test/files/neg/t712.check +++ b/test/files/neg/t712.check @@ -1,5 +1,4 @@ -t712.scala:10: error: value self is not a member of B.this.ParentImpl - Note: implicit method coerce is not applicable here because it comes after the application point and it lacks an explicit result type +t712.scala:10: error: overloaded method coerce needs result type implicit def coerce(p : ParentImpl) = p.self; ^ one error found diff --git a/test/files/neg/t8217-local-alias-requires-rhs.check b/test/files/neg/t8217-local-alias-requires-rhs.check index 0d4f0864ba..d970400ff6 100644 --- a/test/files/neg/t8217-local-alias-requires-rhs.check +++ b/test/files/neg/t8217-local-alias-requires-rhs.check @@ -1,9 +1,9 @@ -t8217-local-alias-requires-rhs.scala:6: error: only classes can have declared but undefined members - type B - ^ t8217-local-alias-requires-rhs.scala:3: error: only classes can have declared but undefined members type A ^ +t8217-local-alias-requires-rhs.scala:6: error: only classes can have declared but undefined members + type B + ^ t8217-local-alias-requires-rhs.scala:14: error: only classes can have declared but undefined members def this(a: Any) = { this(); type C } ^ diff --git a/test/files/neg/t963.check b/test/files/neg/t963.check index 483e53c77d..85b64b0bb5 100644 --- a/test/files/neg/t963.check +++ b/test/files/neg/t963.check @@ -1,12 +1,12 @@ +t963.scala:10: error: type mismatch; + found : AnyRef{def x: Integer} + required: AnyRef{val x: Integer} + val y2 : { val x : java.lang.Integer } = new { def x = new java.lang.Integer(r.nextInt) } + ^ t963.scala:14: error: stable identifier required, but y3.x.type found. val w3 : y3.x.type = y3.x ^ t963.scala:17: error: stable identifier required, but y4.x.type found. val w4 : y4.x.type = y4.x ^ -t963.scala:10: error: type mismatch; - found : AnyRef{def x: Integer} - required: AnyRef{val x: Integer} - val y2 : { val x : java.lang.Integer } = new { def x = new java.lang.Integer(r.nextInt) } - ^ three errors found diff --git a/test/files/pos/t5240.scala b/test/files/pos/t5240.scala index 065d175f2f..ae52c6d69a 100644 --- a/test/files/pos/t5240.scala +++ b/test/files/pos/t5240.scala @@ -1,11 +1,3 @@ - - - - - - package object foo { - var labels: Array[_ <: String] = null - } diff --git a/test/files/presentation/t5708.check b/test/files/presentation/t5708.check index 4b33893e98..0f24d9626b 100644 --- a/test/files/presentation/t5708.check +++ b/test/files/presentation/t5708.check @@ -35,7 +35,7 @@ final def wait(): Unit final def wait(x$1: Long): Unit final def wait(x$1: Long,x$2: Int): Unit final private[this] val CONST_STRING: String("constant") -lazy private[this] var foo: Int +lazy val foo: Int private[package test] def pkgPrivateM: String private[this] val pkgPrivateV: String ================================================================================ diff --git a/test/files/presentation/t8459.check b/test/files/presentation/t8459.check index 336c147141..4c105d2a00 100644 --- a/test/files/presentation/t8459.check +++ b/test/files/presentation/t8459.check @@ -9,6 +9,7 @@ scala.AnyRef { () }; private[this] val bar: F = new F(); + def bar: F = Foo.this.bar; Foo.this.bar.("") } ================================================================================ diff --git a/test/files/run/analyzerPlugins.check b/test/files/run/analyzerPlugins.check index ca0005ea4d..9643079b83 100644 --- a/test/files/run/analyzerPlugins.check +++ b/test/files/run/analyzerPlugins.check @@ -1,4 +1,5 @@ adaptBoundsToAnnots(List( <: Int), List(type T), List(Int @testAnn)) [2] +annotationsConform(Boolean @testAnn, Boolean @testAnn) [2] annotationsConform(Boolean @testAnn, Boolean) [1] annotationsConform(Boolean(false), Boolean @testAnn) [1] annotationsConform(Int @testAnn, ?A) [1] @@ -13,7 +14,7 @@ canAdaptAnnotations(Trees$Select, ?) [1] canAdaptAnnotations(Trees$Select, Boolean @testAnn) [1] canAdaptAnnotations(Trees$Select, Boolean) [1] canAdaptAnnotations(Trees$Select, String @testAnn) [1] -canAdaptAnnotations(Trees$TypeTree, ?) [10] +canAdaptAnnotations(Trees$TypeTree, ?) [8] canAdaptAnnotations(Trees$Typed, ?) [3] canAdaptAnnotations(Trees$Typed, Any) [1] canAdaptAnnotations(Trees$Typed, Int) [1] @@ -24,19 +25,19 @@ pluginsPt(?, Trees$ApplyImplicitView) [2] pluginsPt(?, Trees$Block) [4] pluginsPt(?, Trees$ClassDef) [2] pluginsPt(?, Trees$DefDef) [14] -pluginsPt(?, Trees$Ident) [50] +pluginsPt(?, Trees$Ident) [51] pluginsPt(?, Trees$If) [2] pluginsPt(?, Trees$Literal) [16] -pluginsPt(?, Trees$New) [5] +pluginsPt(?, Trees$New) [6] pluginsPt(?, Trees$PackageDef) [1] pluginsPt(?, Trees$Return) [1] -pluginsPt(?, Trees$Select) [43] +pluginsPt(?, Trees$Select) [45] pluginsPt(?, Trees$Super) [2] pluginsPt(?, Trees$This) [13] pluginsPt(?, Trees$TypeApply) [3] pluginsPt(?, Trees$TypeBoundsTree) [2] pluginsPt(?, Trees$TypeDef) [1] -pluginsPt(?, Trees$TypeTree) [38] +pluginsPt(?, Trees$TypeTree) [32] pluginsPt(?, Trees$Typed) [1] pluginsPt(?, Trees$ValDef) [21] pluginsPt(Any, Trees$Literal) [2] @@ -60,7 +61,7 @@ pluginsPt(String, Trees$Ident) [3] pluginsPt(String, Trees$Literal) [1] pluginsPt(String, Trees$Select) [1] pluginsPt(Unit, Trees$Assign) [1] -pluginsPt(testAnn, Trees$Apply) [5] +pluginsPt(testAnn, Trees$Apply) [6] pluginsTypeSig(, Trees$Template) [2] pluginsTypeSig(class A, Trees$ClassDef) [1] pluginsTypeSig(class testAnn, Trees$ClassDef) [1] @@ -70,16 +71,18 @@ pluginsTypeSig(method foo, Trees$DefDef) [1] pluginsTypeSig(method method, Trees$DefDef) [1] pluginsTypeSig(method nested, Trees$DefDef) [1] pluginsTypeSig(type T, Trees$TypeDef) [2] -pluginsTypeSig(value annotField, Trees$ValDef) [2] +pluginsTypeSig(value annotField, Trees$ValDef) [1] +pluginsTypeSig(value count_=, Trees$ValDef) [1] pluginsTypeSig(value f, Trees$ValDef) [1] -pluginsTypeSig(value inferField, Trees$ValDef) [2] -pluginsTypeSig(value lub1, Trees$ValDef) [2] -pluginsTypeSig(value lub2, Trees$ValDef) [2] +pluginsTypeSig(value inferField, Trees$ValDef) [1] +pluginsTypeSig(value lub1, Trees$ValDef) [1] +pluginsTypeSig(value lub2, Trees$ValDef) [1] pluginsTypeSig(value param, Trees$ValDef) [2] pluginsTypeSig(value str, Trees$ValDef) [1] -pluginsTypeSig(value x, Trees$ValDef) [4] -pluginsTypeSig(value y, Trees$ValDef) [4] -pluginsTypeSig(variable count, Trees$ValDef) [3] +pluginsTypeSig(value x, Trees$ValDef) [3] +pluginsTypeSig(value y, Trees$ValDef) [3] +pluginsTypeSig(variable count, Trees$DefDef) [1] +pluginsTypeSig(variable count, Trees$ValDef) [1] pluginsTypeSigAccessor(value annotField) [1] pluginsTypeSigAccessor(value inferField) [1] pluginsTypeSigAccessor(value lub1) [1] @@ -93,7 +96,7 @@ pluginsTyped(()Object, Trees$Select) [1] pluginsTyped(()String, Trees$Ident) [1] pluginsTyped(()String, Trees$TypeApply) [1] pluginsTyped(()scala.annotation.Annotation, Trees$Select) [1] -pluginsTyped(()testAnn, Trees$Select) [10] +pluginsTyped(()testAnn, Trees$Select) [12] pluginsTyped((str: String)A (param: Double)A, Trees$Select) [1] pluginsTyped((x$1: Any)Boolean (x: Double)Boolean (x: Float)Boolean (x: Long)Boolean (x: Int)Boolean (x: Char)Boolean (x: Short)Boolean (x: Byte)Boolean, Trees$Select) [1] pluginsTyped((x$1: Int)Unit, Trees$Select) [1] @@ -122,7 +125,7 @@ pluginsTyped(Any, Trees$TypeTree) [1] pluginsTyped(AnyRef, Trees$Select) [4] pluginsTyped(Array[Any], Trees$ArrayValue) [1] pluginsTyped(Boolean @testAnn, Trees$Select) [1] -pluginsTyped(Boolean @testAnn, Trees$TypeTree) [4] +pluginsTyped(Boolean @testAnn, Trees$TypeTree) [3] pluginsTyped(Boolean(false), Trees$Literal) [1] pluginsTyped(Boolean, Trees$Apply) [1] pluginsTyped(Boolean, Trees$Select) [3] @@ -139,15 +142,15 @@ pluginsTyped(Int, Trees$Apply) [1] pluginsTyped(Int, Trees$Ident) [1] pluginsTyped(Int, Trees$If) [1] pluginsTyped(Int, Trees$Select) [12] -pluginsTyped(Int, Trees$TypeTree) [13] +pluginsTyped(Int, Trees$TypeTree) [10] pluginsTyped(List[Any], Trees$Apply) [1] pluginsTyped(List[Any], Trees$Select) [1] -pluginsTyped(List[Any], Trees$TypeTree) [3] +pluginsTyped(List[Any], Trees$TypeTree) [2] pluginsTyped(Nothing, Trees$Return) [1] pluginsTyped(Object, Trees$Apply) [1] pluginsTyped(String @testAnn, Trees$Ident) [1] pluginsTyped(String @testAnn, Trees$Select) [1] -pluginsTyped(String @testAnn, Trees$TypeTree) [4] +pluginsTyped(String @testAnn, Trees$TypeTree) [3] pluginsTyped(String(""), Trees$Literal) [2] pluginsTyped(String("huhu"), Trees$Literal) [1] pluginsTyped(String("str") @testAnn, Trees$Typed) [1] @@ -156,13 +159,13 @@ pluginsTyped(String("two"), Trees$Literal) [2] pluginsTyped(String, Trees$Apply) [2] pluginsTyped(String, Trees$Block) [2] pluginsTyped(String, Trees$Select) [7] -pluginsTyped(String, Trees$TypeTree) [7] +pluginsTyped(String, Trees$TypeTree) [6] pluginsTyped(Unit, Trees$Apply) [2] pluginsTyped(Unit, Trees$Assign) [1] pluginsTyped(Unit, Trees$Block) [4] pluginsTyped(Unit, Trees$If) [1] pluginsTyped(Unit, Trees$Literal) [5] -pluginsTyped(Unit, Trees$TypeTree) [1] +pluginsTyped(Unit, Trees$TypeTree) [2] pluginsTyped([A](xs: A*)List[A], Trees$Select) [1] pluginsTyped([T <: Int]=> Int, Trees$Select) [1] pluginsTyped([T0]()T0, Trees$Select) [1] @@ -176,9 +179,9 @@ pluginsTyped(scala.collection.immutable.List.type, Trees$Select) [2] pluginsTyped(scala.collection.immutable.StringOps, Trees$ApplyImplicitView) [2] pluginsTyped(scala.collection.mutable.WrappedArray[Any], Trees$Apply) [1] pluginsTyped(str.type, Trees$Ident) [3] -pluginsTyped(testAnn, Trees$Apply) [5] -pluginsTyped(testAnn, Trees$Ident) [5] -pluginsTyped(testAnn, Trees$New) [5] +pluginsTyped(testAnn, Trees$Apply) [6] +pluginsTyped(testAnn, Trees$Ident) [6] +pluginsTyped(testAnn, Trees$New) [6] pluginsTyped(testAnn, Trees$This) [1] pluginsTyped(testAnn, Trees$TypeTree) [2] pluginsTyped(testAnn.super.type, Trees$Super) [1] diff --git a/test/files/run/compiler-asSeenFrom.check b/test/files/run/compiler-asSeenFrom.check index 7305504115..46ea4d3685 100644 --- a/test/files/run/compiler-asSeenFrom.check +++ b/test/files/run/compiler-asSeenFrom.check @@ -332,11 +332,6 @@ value dZ { // after parser val cD: ll.C[List[T3]] } -value dZ { // after parser - private[this] val cD: ll.C[List[T3]] - val cD: ll.C[List[T3]] -} - value dZ { // after uncurry private[this] val cD: ll.C[List[T3]] val cD(): ll.C[List[T3]] @@ -347,11 +342,9 @@ value dZ { // after erasure val cD(): ll.C } -value jZ { // after parser - def thisI(): I.this.type - def thisC(): C.this.type - def t2(): T2 - def t1(): T1 +value dZ { // after parser + private[this] val cD: ll.C[List[T3]] + val cD: ll.C[List[T3]] } value jZ { // after parser @@ -393,6 +386,13 @@ value jZ { // after flatten def t1(): Object } +value jZ { // after parser + def thisI(): I.this.type + def thisC(): C.this.type + def t2(): T2 + def t1(): T1 +} + method kz { // after parser def thisI(): I.this.type def thisC(): C.this.type diff --git a/test/files/run/existential-rangepos.check b/test/files/run/existential-rangepos.check index 1212b60bae..984baeaaf8 100644 --- a/test/files/run/existential-rangepos.check +++ b/test/files/run/existential-rangepos.check @@ -7,7 +7,7 @@ }; [24:51]private[this] val foo: [28]Set[_ <: T] = [47:51]null; [28] def foo: [28]Set[_ <: T] = [28][28]A.this.foo; - [54:74] def bar: [58]Set[_ <: T] + [54:74] val bar: [58]Set[_ <: T] } } diff --git a/test/files/run/idempotency-lazy-vals.check b/test/files/run/idempotency-lazy-vals.check index 15afa5303c..3a6f1a7ef0 100644 --- a/test/files/run/idempotency-lazy-vals.check +++ b/test/files/run/idempotency-lazy-vals.check @@ -5,19 +5,11 @@ C.super.(); () }; - lazy private[this] val x: Int = _; - lazy def x: Int = { - C.this.x = 2; - C.this.x - }; - lazy private[this] val y: Int = _; - implicit lazy def y: Int = { - C.this.y = 3; - C.this.y - } + lazy val x: Int = 2; + implicit lazy val y: Int = 3 }; val c: C = new C(); import c._; c.x.*(Predef.implicitly[Int](c.y)) } -error! +6 diff --git a/test/files/run/lazy-locals.check b/test/files/run/lazy-locals.check index 4565326bea..0a3a85ead6 100644 --- a/test/files/run/lazy-locals.check +++ b/test/files/run/lazy-locals.check @@ -1,9 +1,6 @@ lazy-locals.scala:153: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses { ^ -lazy-locals.scala:159: warning: a pure expression does nothing in statement position; multiline expressions may require enclosing parentheses - { - ^ forced lazy val q q = 10 forced lazy val t diff --git a/test/files/run/showraw_mods.check b/test/files/run/showraw_mods.check index ff77d22adf..5afd7a438f 100644 --- a/test/files/run/showraw_mods.check +++ b/test/files/run/showraw_mods.check @@ -1 +1 @@ -Block(List(ClassDef(Modifiers(ABSTRACT | DEFAULTPARAM/TRAIT), TypeName("C"), List(), Template(List(Ident(TypeName("AnyRef"))), noSelfType, List(DefDef(Modifiers(), TermName("$init$"), List(), List(List()), TypeTree(), Block(List(), Literal(Constant(())))), DefDef(Modifiers(PRIVATE | METHOD | LOCAL | STABLE | ACCESSOR), TermName("x"), List(), List(), TypeTree(), Literal(Constant(2))), DefDef(Modifiers(METHOD | ACCESSOR), TermName("y"), List(), List(), TypeTree(), Select(This(TypeName("C")), TermName("x"))), DefDef(Modifiers(METHOD | ACCESSOR), TermName("y_$eq"), List(), List(List(ValDef(Modifiers(PARAM | SYNTHETIC), TermName("x$1"), TypeTree(), EmptyTree))), TypeTree(), EmptyTree), ValDef(Modifiers(LAZY), TermName("z"), TypeTree(), Select(This(TypeName("C")), TermName("y"))))))), Literal(Constant(()))) +Block(List(ClassDef(Modifiers(ABSTRACT | DEFAULTPARAM/TRAIT), TypeName("C"), List(), Template(List(Ident(TypeName("AnyRef"))), noSelfType, List(DefDef(Modifiers(), TermName("$init$"), List(), List(List()), TypeTree(), Block(List(), Literal(Constant(())))), ValDef(Modifiers(PRIVATE | LOCAL), TermName("x"), TypeTree(), Literal(Constant(2))), ValDef(Modifiers(MUTABLE), TermName("y"), TypeTree(), Select(This(TypeName("C")), TermName("x"))), DefDef(Modifiers(METHOD | ACCESSOR), TermName("y_$eq"), List(), List(List(ValDef(Modifiers(PARAM | SYNTHETIC), TermName("x$1"), TypeTree(), EmptyTree))), TypeTree(), EmptyTree), ValDef(Modifiers(LAZY), TermName("z"), TypeTree(), Select(This(TypeName("C")), TermName("y"))))))), Literal(Constant(()))) diff --git a/test/files/run/t6023.check b/test/files/run/t6023.check index ee93565234..dd6d8f1f1c 100644 --- a/test/files/run/t6023.check +++ b/test/files/run/t6023.check @@ -1,12 +1,12 @@ { abstract trait Foo extends AnyRef { - def a: Int + val a: Int }; () } { abstract trait Foo extends AnyRef { - def a: Int + val a: Int }; () } diff --git a/test/files/run/t6733.check b/test/files/run/t6733.check index 7062301c56..811a7d8f70 100644 --- a/test/files/run/t6733.check +++ b/test/files/run/t6733.check @@ -4,7 +4,6 @@ method pri2a: isPrivateThis = true, isProtectedThis = false variable pri3a: isPrivateThis = true, isProtectedThis = false variable pri3a: isPrivateThis = true, isProtectedThis = false lazy value pri4a: isPrivateThis = true, isProtectedThis = false -lazy value pri4a: isPrivateThis = true, isProtectedThis = false type Pri5a: isPrivateThis = true, isProtectedThis = false class Pri6: isPrivateThis = true, isProtectedThis = false trait Pri7: isPrivateThis = true, isProtectedThis = false @@ -18,7 +17,6 @@ variable pro3a: isPrivateThis = false, isProtectedThis = true variable pro3b: isPrivateThis = false, isProtectedThis = true variable pro3b: isPrivateThis = false, isProtectedThis = true lazy value pro4a: isPrivateThis = false, isProtectedThis = true -lazy value pro4a: isPrivateThis = true, isProtectedThis = false type Pro5a: isPrivateThis = false, isProtectedThis = true type Pro5b: isPrivateThis = false, isProtectedThis = true class Pro6: isPrivateThis = false, isProtectedThis = true diff --git a/test/files/run/trait-fields-override-lazy.check b/test/files/run/trait-fields-override-lazy.check new file mode 100644 index 0000000000..9e4a9fe6c2 --- /dev/null +++ b/test/files/run/trait-fields-override-lazy.check @@ -0,0 +1,2 @@ +warning: there was one feature warning; re-run with -feature for details +ok diff --git a/test/files/run/trait-fields-override-lazy.scala b/test/files/run/trait-fields-override-lazy.scala new file mode 100644 index 0000000000..2c1cf0e3b0 --- /dev/null +++ b/test/files/run/trait-fields-override-lazy.scala @@ -0,0 +1,13 @@ +trait T { + protected lazy val lv: Boolean = ??? +} + +object Test extends App { + val overrideLazy = new T { + override lazy val lv = true + def foo = lv + } + + assert(overrideLazy.foo) + println("ok") +} diff --git a/test/files/scalacheck/quasiquotes/TypecheckedProps.scala b/test/files/scalacheck/quasiquotes/TypecheckedProps.scala index fe07893a36..07bff40f13 100644 --- a/test/files/scalacheck/quasiquotes/TypecheckedProps.scala +++ b/test/files/scalacheck/quasiquotes/TypecheckedProps.scala @@ -103,7 +103,7 @@ object TypecheckedProps extends QuasiquoteProperties("typechecked") val lazyName = TermName("x") val lazyRhsVal = 42 val lazyRhs = Literal(Constant(lazyRhsVal)) - val q"{ $_ ; $mods val $pname: $_ = { $_ = $rhs ; $_ } }" = typecheck(q"{lazy val $lazyName = $lazyRhsVal}") + val q"{ $mods val $pname: $_ = $rhs }" = typecheck(q"{lazy val $lazyName = $lazyRhsVal}") assert(pname == lazyName) assert(rhs ≈ lazyRhs) diff --git a/test/junit/scala/reflect/internal/PrintersTest.scala b/test/junit/scala/reflect/internal/PrintersTest.scala index 234f22e9fb..722062ba21 100644 --- a/test/junit/scala/reflect/internal/PrintersTest.scala +++ b/test/junit/scala/reflect/internal/PrintersTest.scala @@ -151,7 +151,7 @@ class BasePrintTest { |else | ((a.toString): String)""", typedCode=sm""" - |val a: Int = 1; + |val a = 1; |if (PrintersContext.this.a.>(1)) | ((PrintersContext.this.a): scala.Int) |else @@ -864,7 +864,7 @@ class TraitPrintTest { @Test def testTraitWithSelf2 = assertPrintedCode(sm""" |trait X { self: scala.Cloneable with scala.Serializable => - | val x: Int = 1 + | val x: scala.Int = 1 |}""") @Test def testTraitTypeParams = assertPrintedCode("trait X[A, B]") @@ -903,7 +903,7 @@ class TraitPrintTest { | type Foo; | type XString = scala.Predef.String |} with scala.Serializable { - | val z: Int = 7 + | val z: scala.Int = 7 |}""") @Test def testTraitWithSingletonTypeTree = assertPrintedCode(sm""" @@ -1008,27 +1008,16 @@ class ValAndDefPrintTest { @Test def testDef9 = assertPrintedCode("def a(x: scala.Int)(implicit z: scala.Double, y: scala.Float): scala.Unit = ()") - @Test def testDefWithLazyVal1 = assertResultCode( - code = "def a = { lazy val test: Int = 42 }")( - parsedCode = sm""" + @Test def testDefWithLazyVal1 = assertPrintedCode(sm""" |def a = { - | lazy val test: Int = 42; + | lazy val test: scala.Int = 42; | () |} - """, - typedCode = sm""" - |def a = { - | lazy val test$$lzy: scala.Int = _; - | lazy val test: Int = { - | test$$lzy = 42; - | test$$lzy - | }; - | () - |}""") + """) @Test def testDefWithLazyVal2 = assertPrintedCode(sm""" |def a = { - | lazy val test: Unit = { + | lazy val test: scala.Unit = { | scala.Predef.println(); | scala.Predef.println() | }; diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala index 5bd2ce68f1..85b44d9fa0 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlineWarningTest.scala @@ -57,7 +57,7 @@ class InlineWarningTest extends BytecodeTesting { assert(c == 1, c) } - @Test +// @Test -- TODO def mixedWarnings(): Unit = { val javaCode = """public class A { diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala index 29a23df784..9999cdb376 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala @@ -726,7 +726,7 @@ class InlinerTest extends BytecodeTesting { """sealed trait T { | lazy val a = 0 | val b = 1 - | final lazy val c = 2 + | final lazy val c: Int = 2 // make sure it doesn't get a constant type | final val d = 3 | final val d1: Int = 3 | @@ -740,7 +740,7 @@ class InlinerTest extends BytecodeTesting { |trait U { // not sealed | lazy val a = 0 | val b = 1 - | final lazy val c = 2 + | final lazy val c: Int = 2 // make sure it doesn't get a constant type | final val d = 3 | final val d1: Int = 3 | @@ -766,7 +766,7 @@ class InlinerTest extends BytecodeTesting { val m1 = getMethod(c, "m1") assertInvoke(m1, "T", "a") assertInvoke(m1, "T", "b") - assertInvoke(m1, "T", "c") +// assertInvoke(m1, "T", "c") -- this lazy val is implemented purely in the trait, as it's constant, so it *can* be inlined assertNoInvoke(getMethod(c, "m2")) @@ -779,7 +779,7 @@ class InlinerTest extends BytecodeTesting { val m4 = getMethod(c, "m4") assertInvoke(m4, "U", "a") assertInvoke(m4, "U", "b") - assertInvoke(m4, "U", "c") +// assertInvoke(m4, "U", "c") -- this lazy val is implemented purely in the trait, as it's constant, so it *can* be inlined assertNoInvoke(getMethod(c, "m5")) diff --git a/test/scaladoc/run/t7767.scala b/test/scaladoc/run/t7767.scala index 6c9ceb511d..433fc5c0c4 100644 --- a/test/scaladoc/run/t7767.scala +++ b/test/scaladoc/run/t7767.scala @@ -4,15 +4,49 @@ import scala.tools.partest.ScaladocModelTest object Test extends ScaladocModelTest { override def code = """ - class Docable extends { /**Doc*/ val foo = 0 } with AnyRef - """ + class CEarly extends { /**CEarly_Doc_foo*/ val foo = 0 } with AnyRef + trait TEarly extends { /**TEarly_Doc_foo*/ val foo = 0 } with AnyRef + class C { + /**C_Doc_sigInferred*/ val sigInferred = 0 + /**C_Doc_const*/ final val const = 0 + /**C_Doc_varr*/ var varr: Any = null + /**C_Doc_abs*/ val abs: Int + /**C_Doc_absVar*/ var absVar: Any + /**C_Doc_lazyValInferred*/ lazy val lazyValInferred = 0 + /**C_Doc_lazyValConst*/ final lazy val lazyValConst = 0 + /**C_Doc_lazyValUnit*/ lazy val lazyValUnit: Unit = println() + /**C_Doc_lazyVal*/ lazy val lazyVal: Int = 0 + } + trait T { + /**T_Doc_sigInferred*/ val sigInferred = 0 + /**T_Doc_const*/ final val const = 0 + /**T_Doc_varr*/ var varr: Any = null + /**T_Doc_abs*/ val abs: Int + /**T_Doc_absVar*/ var absVar: Any + /**T_Doc_lazyValInferred*/ lazy val lazyValInferred = 0 + /**T_Doc_lazyValConst*/ final lazy val lazyValConst = 0 + /**T_Doc_lazyValUnit*/ lazy val lazyValUnit: Unit = println() + /**T_Doc_lazyVal*/ lazy val lazyVal: Int = 0 + }""" // no need for special settings def scaladocSettings = "" + def assertDoc(classEntity: DocTemplateEntity, valName: String) = { + import access._ + val comment = classEntity._value(valName).comment.map(_.body.toString.trim).getOrElse("") + val className = classEntity.name + val marker = s"${className}_Doc_${valName}" + assert(comment.contains(marker), s"Expected $marker in comment for $valName in $className, found: $comment.") + } + def testModel(rootPackage: Package) = { import access._ - val comment = rootPackage._class("Docable")._value("foo").comment.map(_.body.toString.trim).getOrElse("") - assert(comment.contains("Doc"), comment) + assertDoc(rootPackage._class("CEarly"), "foo") + assertDoc(rootPackage._trait("TEarly"), "foo") + + val valNames = List("sigInferred", "const", "varr", "abs", "absVar", "lazyValInferred", "lazyValConst", "lazyValUnit", "lazyVal") + val entities = List(rootPackage._class("C"), rootPackage._trait("T")) + for (e <- entities; vn <- valNames) assertDoc(e, vn) } } -- cgit v1.2.3 From 63f7b357f787b96ddf0440c779b1937844cbbb52 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 12 Aug 2016 15:50:57 -0700 Subject: SI-8873 don't propagate primary ctor arg to case class's apply Final implementation based on feedback by Jason --- src/compiler/scala/tools/nsc/typechecker/Namers.scala | 11 +++++++++++ src/compiler/scala/tools/nsc/typechecker/Unapplies.scala | 1 + test/files/pos/t8873.scala | 1 + 3 files changed, 13 insertions(+) create mode 100644 test/files/pos/t8873.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 033b6cc0cd..fe7a4d5fef 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -686,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) } @@ -813,6 +815,15 @@ trait Namers extends MethodSynthesis { classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } + def caseApplyMethodCompleter(tree: DefDef, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) + + // 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)) + + 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) 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/test/files/pos/t8873.scala b/test/files/pos/t8873.scala new file mode 100644 index 0000000000..e2f0a5fad2 --- /dev/null +++ b/test/files/pos/t8873.scala @@ -0,0 +1 @@ +case class X(@volatile var x:Int) -- cgit v1.2.3 From b5c5f30d1838d29e409d0ffab40966c996c644dd Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 12 Aug 2016 17:09:15 -0700 Subject: optimize/simplify erasure of class info type --- .../scala/reflect/internal/transform/Erasure.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 62ca50d035..a9e26c0f7d 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) } -- cgit v1.2.3 From 73f08e54d72ac60b69b667720504ab23fe430c2f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 15 Aug 2016 09:45:33 -0700 Subject: [refactor] lazy val expansion in mixins/lazyvals Towards expanding lazy vals and modules during fields phase. --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 17 ++-- .../scala/tools/nsc/transform/LazyVals.scala | 98 ++++++++++------------ src/compiler/scala/tools/nsc/transform/Mixin.scala | 56 ++++--------- 3 files changed, 68 insertions(+), 103 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 5dddf30c96..ac47b3c464 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -234,16 +234,15 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { } /** 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. + * synchronizes on `attrThis`. Additional statements can be included after initialization, + * (outside the synchronized block). * - * @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) - + * The idiom works only if the condition is using a volatile field. + * + * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html + * + * TODO: update to state of the art on java 8 (https://shipilev.net/blog/2014/safe-public-construction/) + */ 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 diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala index fc7999bf3b..ba2f6082e0 100644 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala @@ -111,14 +111,14 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD 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) + val (rhs1, sDef) = mkLazyLocalDef(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)) + val (fastPath, slowPath) = mkDoubleCheckedLocking(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) @@ -213,28 +213,27 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD } } - def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): Tree = { + def mkDoubleCheckedLocking(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): (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))) - } + + val slowPathDef: Tree = { + 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(gen.mkAttributedThis(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) + (If(cond, Apply(Ident(defSym), Nil), retVal), slowPathDef) } /** return a 'lazified' version of rhs. Rhs should conform to the @@ -271,48 +270,41 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD * } * } */ - 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) + val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil + private def mkLazyLocalDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = { + /** Return the symbol corresponding of the right bitmap int inside meth, + * given offset. + */ + val bitmapSym = { + val n = offset / FLAGS_PER_BYTE + val bmps = bitmaps(methOrClass) + if (bmps.length > n) + bmps(n) + else { + val sym = methOrClass.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), methOrClass.pos).setInfo(ByteTpe) + enteringTyper { + sym addAnnotation VolatileAttr + } + + bitmaps(methOrClass) = (sym :: bmps).reverse + sym + } + } - def mkBlock(stmt: Tree) = BLOCK(stmt, mkSetFlag(bitmapSym, mask, bitmapRef), UNIT) + val mask = LIT(1 << (offset % FLAGS_PER_BYTE)) + val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym) 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) + + val (stmt, res) = tree match { + case Block(List(assignment), res) if !lazyUnit(lazyVal) => (assignment, res) + case rhs => (rhs, UNIT) } + val block = Block(List(stmt, bitmapRef === (bitmapRef GEN_| (mask, bitmapKind))), UNIT) def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) - val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) + val lazyDefs = mkDoubleCheckedLocking(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 4be611b747..f889f1244a 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -636,41 +636,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - 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 @@ -708,20 +673,29 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * 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 mkLazyMemberDef(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)) + val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? + val slowPathSym = + clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) + + def thisRef = gen.mkAttributedThis(clazz) + def cond = mkTest(clazz, mask, bitmapSym, equalToZero = true, kind) + + val statsToSynch = init ::: List(mkSetFlag(clazz, offset, lzyVal, kind), UNIT) + val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) + addDef(pos, DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(currentOwner -> slowPathSym)), retVal))) + + typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathSym), Nil), retVal)) } def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { @@ -752,10 +726,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { 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 t if isUnitGetter(sym) => mkLazyMemberDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) case Block(stats, res) => - mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) + mkLazyMemberDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) case t => t // pass specialized lazy vals through } -- cgit v1.2.3 From 36ce99acd47e50fb0eb3c933309c0248f394e550 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 15 Aug 2016 12:56:04 -0700 Subject: [refactor] corral checkinit logic Just moving some code around to make it comprehensible. --- src/compiler/scala/tools/nsc/transform/Mixin.scala | 108 ++++++++++----------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index f889f1244a..95fe30794e 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -92,6 +92,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { field.isLazy && field.isMethod && !field.isDeferred } + def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr + /** 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 @@ -101,8 +103,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * Note: The `checkinit` option does not check if transient fields are initialized. */ private def needsInitFlag(sym: Symbol) = ( - settings.checkInit - && sym.isGetter + sym.isGetter && !sym.isInitializedToDefault && !isConstantType(sym.info.finalResultType) // SI-4742 && !sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY) @@ -467,28 +468,21 @@ 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. - */ + * 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 + */ 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.accessedOrSelf hasAnnotation TransientAttr) { - if (isNormal) BITMAP_TRANSIENT - else BITMAP_CHECKINIT_TRANSIENT - } else { - if (isNormal) BITMAP_NORMAL - else BITMAP_CHECKINIT - } + + if (isFieldWithBitmap(field)) + if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL + else if (!field.isDeferred && settings.checkInit && needsInitFlag(field)) + if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + else NO_NAME } /** Add all new definitions to a non-trait class @@ -718,51 +712,47 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * 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 + def addInitCheck(clazz: Symbol)(stat: DefDef): DefDef = { + val sym = stat.symbol - if (!clazz.isTrait && sym.isLazy && !isEmpty) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnitGetter(sym) => mkLazyMemberDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) + if (!clazz.isTrait && sym.isLazy && stat.rhs != EmptyTree) { + assert(fieldOffset contains sym, sym) + deriveDefDef(stat) { + case t if isUnitGetter(sym) => mkLazyMemberDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) - case Block(stats, res) => - mkLazyMemberDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) + case Block(stats, res) => + mkLazyMemberDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) - case t => t // pass specialized lazy vals through - } + 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 (!settings.checkInit) stat + else if (needsInitFlag(sym) && stat.rhs != EmptyTree && !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 + else if (sym.isConstructor) { + deriveDefDef(stat)(addInitBits(clazz, _)) } + else if (!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 + } + // @pre settings.checkInit class AddInitBitsTransformer(clazz: Symbol) extends Transformer { private def checkedGetter(lhs: Tree) = { val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitAndHasOffset(sym)) { + if (needsInitFlag(sym) && (fieldOffset contains sym)) { debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym, bitmapKind(sym))) } @@ -774,7 +764,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { super.transformStats( stats flatMap { case stat @ Assign(lhs @ Select(This(_), _), rhs) => stat :: checkedGetter(lhs) - // remove initialization for default values + // 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) }, @@ -785,6 +775,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { /* Adds statements to set the 'init' bit for each field initialized * in the body of a constructor. + * @pre settings.checkInit */ def addInitBits(clazz: Symbol, rhs: Tree): Tree = new AddInitBitsTransformer(clazz) transform rhs @@ -815,7 +806,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } buildBitmapOffsets() - var stats1 = addCheckedGetters(clazz, stats) + var stats1 = stats mapConserve { + case dd: DefDef => addInitCheck(clazz)(dd) + case stat => stat + } def getterBody(getter: Symbol) = { assert(getter.isGetter) @@ -824,7 +818,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { fieldAccess(getter) } - if (!needsInitFlag(getter)) readValue + if (!(settings.checkInit && needsInitFlag(getter))) readValue else mkCheckedAccessor(clazz, readValue, fieldOffset(getter), getter.pos, getter) } @@ -833,7 +827,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz") val setInitFlag = - if (!needsInitFlag(getter)) Nil + if (!(settings.checkInit && needsInitFlag(getter))) Nil else List(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter))) Block(Assign(fieldAccess(setter), Ident(setter.firstParam)) :: setInitFlag : _*) -- cgit v1.2.3 From ef2a898ac5faf136550187bf7cfcdba0f6b6ed85 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 15 Aug 2016 17:11:45 -0700 Subject: [refactor] corral init bits some more More code motion. --- src/compiler/scala/tools/nsc/transform/Mixin.scala | 765 +++++++++++---------- 1 file changed, 410 insertions(+), 355 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 95fe30794e..56c10218eb 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -12,11 +12,67 @@ import scala.annotation.tailrec import scala.collection.mutable // TODO: move lazy vals bitmap creation to lazy vals phase now that lazy vals are mixed in during fields -abstract class Mixin extends InfoTransform with ast.TreeDSL { +trait InitBitmaps extends Transform with ast.TreeDSL { import global._ import definitions._ import CODE._ + /** 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. + */ + val fieldOffset = perRunCaches.newMap[Symbol, Int]() + + val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() + + class InitializationTransformer extends Transformer { + /** The typer */ + protected var localTyper: erasure.Typer = _ + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) + + trait AccessorInitialization { + protected val clazz: Symbol + protected val templ: Template + + def addDefDef(sym: Symbol, rhs: Tree = EmptyTree): Unit + def addValDef(sym: Symbol, rhs: Tree = EmptyTree): Unit + + type WithInitChecks + def addInitChecks(stats: List[Tree]): WithInitChecks + def implementWithNewDefs(stats: WithInitChecks): List[Tree] + + 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) + + def completeSuperAccessor(stat: Tree): Tree + } + + } +} + + +abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { + import global._ + import definitions._ + import CODE._ + + /** The name of the phase: */ val phaseName: String = "mixin" @@ -85,33 +141,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { && (!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 - } - def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr - - /** 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) = ( - 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 +186,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. */ @@ -321,7 +350,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { // mixin accessor for constructor parameter // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) - val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) + cloneAndAddMixinMember(mixinClass, mixinMember) + val name = mixinMember.name if (!nme.isSetterName(name)) { @@ -368,82 +398,17 @@ 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 InitializationTransformer { /** 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). @@ -468,93 +433,163 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } + def accessorInitialization(clazz: Symbol, templ: Template): AccessorInitialization = + if (settings.checkInit) new AddsCheckInitDefs(clazz, templ) + else new StandardAccessorInitialization(clazz, templ) - /** 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 - */ - def bitmapCategory(field: Symbol): Name = { - import nme._ - - if (isFieldWithBitmap(field)) - if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL - else if (!field.isDeferred && settings.checkInit && needsInitFlag(field)) - if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT - else NO_NAME - } - /** Add all new definitions to a non-trait class - * These fall into the following categories: - * - for a trait interface: - * - abstract accessors for all fields in the implementation class - * - for a non-trait class: - * - A field for every in a mixin class - * - Setters and getters for such fields - * - getters for mixed in lazy fields are completed - * - module variables and module creators for every module in a mixin class - * (except if module is lifted -- in this case the module variable - * is local to some function, and the creator method is static.) - * - A super accessor for every super accessor in a mixin class - * - Forwarders for all methods that are implemented statically - * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) - * - * @param clazz The class to which definitions are added - */ - private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { - val newDefs = mutable.ListBuffer[Tree]() + protected class StandardAccessorInitialization(val clazz: Symbol, val templ: Template) extends AccessorInitialization { + /** Map lazy values to the fields they should null after initialization. */ + private lazy val lazyValNullables: 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 { + 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 + } toMap + } + + val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() + // check what fields can be nulled for + for ((field, users) <- singleUseFields; lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr)) + map(lazyFld) += field + + map.mapValues(_.toList sortBy (_.id)).toMap + } + + // overridden in AddsCheckInitDefs + protected def addCheckedInitCheck(stat: DefDef): DefDef = stat + + // ByteClass, IntClass, LongClass + protected def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) + + /** 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(field: Symbol): Name = { + import nme._ + + 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 + } + + if (isFieldWithBitmap(field)) + if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL + else if (needsInitFlag(field) && !field.isDeferred) + if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + else NO_NAME + } - /* Attribute given tree and anchor at given position */ - def attributedDef(pos: Position, tree: Tree): Tree = { + private val _newDefs = mutable.ListBuffer[Tree]() + + /** Attribute given tree and anchor at given position */ + private 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) = + + /** 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 tree at given position as new definition */ - def addDef(pos: Position, tree: Tree) { - newDefs += attributedDef(pos, tree) - } + /** Add tree at given position as new definition */ + private def addDef(pos: Position, tree: ValOrDefDef): Unit = + _newDefs += attributedDef(pos, tree) - /* Add new method definition. - * - * @param sym The method symbol. - * @param rhs The method body. - */ + /** 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]) = { + + + protected def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr + + // overridden in AddsCheckInitDefs + protected def needsInitFlag(sym: Symbol): Boolean = false + + protected def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + + private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { + case BooleanClass => 1 + case ByteClass => 8 + case IntClass => 32 + case LongClass => 64 + } + + + type WithInitChecks = List[Tree] + + /** 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: WithInitChecks): 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 + 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. - */ + /** 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)))) @@ -569,16 +604,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * 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) + def bitmapFor(offset: RunId, field: global.Symbol): Symbol = { + val category = bitmapCategory(field) val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName - val sym = clazz0.info.decl(bitmapName) + val sym = clazz.info.decl(bitmapName) assert(!sym.isOverloaded, sym) def createBitmap: Symbol = { - val bitmapKind = bitmapKindForCategory(category) - val sym = clazz0.newVariable(bitmapName, clazz0.pos) setInfo bitmapKind.tpe + val bitmapKind = bitmapKindForCategory(category) + val sym = clazz.newVariable(bitmapName, clazz.pos) setInfo bitmapKind.tpe enteringTyper(sym addAnnotation VolatileAttr) category match { @@ -591,89 +626,89 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } sym setFlag PrivateLocal - clazz0.info.decls.enter(sym) - addDef(clazz0.pos, init) + clazz.info.decls.enter(sym) + addDef(clazz.pos, init) sym } sym orElse createBitmap } - def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = { + protected def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = { def realOffset = offset % flagsPerBitmap(sym) - if (kind == LongClass ) LIT(1L << realOffset) else LIT(1 << realOffset) + 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)) + def mkSetFlag(offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { + val bmp = bitmapFor(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) + /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the + * precise comparison operator depending on the value of 'equalToZero'. + */ + protected def mkTest(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 + if (equalToZero) NOT(bitmapTree) + else bitmapTree case _ => - if (equalToZero) lhs GEN_== (ZERO, kind) - else lhs GEN_!= (ZERO, kind) + if (equalToZero) lhs GEN_==(ZERO, kind) + else lhs GEN_!=(ZERO, kind) } } - /* 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 = ) - * @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$ = - * 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 mkLazyMemberDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): 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 lzyVal The symbol of this lazy field + * @param init The tree which initializes the field ( f = ) + * @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$ = + * 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. + */ + private def mkLazyMemberDef(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) - val nulls = lazyValNullables(lzyVal).toList sortBy (_.id) map nullify + val bitmapSym = bitmapFor(offset, lzyVal) + val kind = bitmapKind(lzyVal) + val mask = maskForOffset(offset, lzyVal, kind) + val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify if (nulls.nonEmpty) log("nulling fields inside " + lzyVal + ": " + nulls) @@ -683,78 +718,101 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) def thisRef = gen.mkAttributedThis(clazz) - def cond = mkTest(clazz, mask, bitmapSym, equalToZero = true, kind) + def cond = mkTest(mask, bitmapSym, equalToZero = true, kind) - val statsToSynch = init ::: List(mkSetFlag(clazz, offset, lzyVal, kind), UNIT) + val statsToSynch = init ::: List(mkSetFlag(offset, lzyVal, kind), UNIT) val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) addDef(pos, DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(currentOwner -> slowPathSym)), retVal))) typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathSym), Nil), 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 addInitCheck(clazz: Symbol)(stat: DefDef): DefDef = { - val sym = stat.symbol - - if (!clazz.isTrait && sym.isLazy && stat.rhs != EmptyTree) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnitGetter(sym) => mkLazyMemberDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) - - case Block(stats, res) => - mkLazyMemberDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) + def addInitChecks(stats: List[Tree]): WithInitChecks = { + /* 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: Unit = { + def fold(fields: List[Symbol], category: Name) = { + var idx = 0 + fields foreach { f => + fieldOffset(f) = idx + idx += 1 + } - case t => t // pass specialized lazy vals through + 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) } } - else if (!settings.checkInit) stat - else if (needsInitFlag(sym) && stat.rhs != EmptyTree && !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 (!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 + + /** 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 addInitCheck(stat: DefDef): DefDef = { + val sym = stat.symbol + + if (!clazz.isTrait && sym.isLazy && stat.rhs != EmptyTree) { + assert(fieldOffset contains sym, sym) + deriveDefDef(stat) { + case t if isUnitGetter(sym) => + mkLazyMemberDef(sym, List(t), UNIT, fieldOffset(sym)) + + case Block(stats, res) => + mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) + + case t => t // pass specialized lazy vals through + } + } + else addCheckedInitCheck(stat) } - else stat + buildBitmapOffsets + + stats mapConserve { + case dd: DefDef => addInitCheck(dd) + case stat => stat + } } - // @pre settings.checkInit - class AddInitBitsTransformer(clazz: Symbol) extends Transformer { + } + + protected class AddsCheckInitDefs(clazz: Symbol, templ: Template) extends StandardAccessorInitialization(clazz, templ) { + /** 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. + */ + override 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) + ) + + object addInitBitsTransformer extends Transformer { private def checkedGetter(lhs: Tree) = { val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) if (needsInitFlag(sym) && (fieldOffset contains sym)) { debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym, bitmapKind(sym))) + List(localTyper typed mkSetFlag(fieldOffset(sym), sym, bitmapKind(sym))) } else Nil } @@ -763,78 +821,89 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // that self is anything in particular? super.transformStats( stats flatMap { - case stat @ Assign(lhs @ Select(This(_), _), rhs) => stat :: checkedGetter(lhs) + case stat@Assign(lhs@Select(This(_), _), rhs) => stat :: checkedGetter(lhs) // 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) + 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. - * @pre settings.checkInit - */ - 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 - } + /** Adds statements to set the 'init' bit for each field initialized + * in the body of a constructor. + * @pre settings.checkInit + */ + def addInitBits(rhs: Tree): Tree = addInitBitsTransformer transform rhs - 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 + override def addCheckedInitCheck(stat: DefDef): DefDef = { + val sym = stat.symbol + if (needsInitFlag(sym) && stat.rhs != EmptyTree && !clazz.hasFlag(TRAIT)) { + assert(fieldOffset contains sym, sym) + deriveDefDef(stat)(rhs => + (mkCheckedAccessor(_: Tree, fieldOffset(sym), stat.pos, sym)) ( + if (isUnitGetter(sym)) UNIT else rhs + ) + ) } - clazz.info.decls.toList groupBy bitmapCategory foreach { - case (nme.NO_NAME, _) => () - case (category, fields) => fold(fields, category) + else if (sym.isConstructor) { + deriveDefDef(stat)(addInitBits) } - } - buildBitmapOffsets() - var stats1 = stats mapConserve { - case dd: DefDef => addInitCheck(clazz)(dd) - case stat => stat + else if (!clazz.isTrait && sym.isSetter) { + val getter = sym.getterIn(clazz) + if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) + deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) + else stat + } + else stat } - def getterBody(getter: Symbol) = { - assert(getter.isGetter) - val readValue = { - assert(getter.hasFlag(PARAMACCESSOR)) - fieldAccess(getter) - } + def mkCheckedAccessor(retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { + val sym = fieldSym.getterIn(fieldSym.owner) + val bitmapSym = bitmapFor(offset, sym) + val kind = bitmapKind(sym) + val mask = maskForOffset(offset, sym, kind) + val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" + val result = + IF(mkTest(mask, bitmapSym, equalToZero = false, kind)). + THEN(retVal). + ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) - if (!(settings.checkInit && needsInitFlag(getter))) readValue - else mkCheckedAccessor(clazz, readValue, fieldOffset(getter), getter.pos, getter) + typedPos(pos)(BLOCK(result, retVal)) } - 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 (!(settings.checkInit && needsInitFlag(getter))) Nil - else List(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter))) + // TODO: need to run this on all accessor bodies (after fields refactoring, this is only run on accessors mixed in during mixins, which is only PRESUPER | PARAMACCESSOR) + override def getterBody(getter: Symbol) = { + if (!needsInitFlag(getter)) super.getterBody(getter) + else mkCheckedAccessor(super.getterBody(getter), fieldOffset(getter), getter.pos, getter) + } - Block(Assign(fieldAccess(setter), Ident(setter.firstParam)) :: setInitFlag : _*) + override def setterBody(setter: Symbol, getter: Symbol) = { + if (!needsInitFlag(getter)) super.setterBody(setter, getter) + else Block(List(super.setterBody(setter, getter)), mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter))) } + } + - def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed) + /** Add all new definitions to a non-trait class + * + * These fall into the following categories: + * - for a trait interface: + * - abstract accessors for all paramaccessor or early initialized fields + * - for a non-trait class: + * - field and accessor implementations for each inherited paramaccessor or early initialized field + * - A super accessor for every super accessor in a mixin class + * - Forwarders for all methods that are implemented statically + * + * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) + * + * @param clazz The class to which definitions are added + */ + private def addNewDefs(clazz: Symbol, stats: List[Tree], accessorInit: AccessorInitialization): List[Tree] = { + import accessorInit._ + val statsWithInitChecks = addInitChecks(stats) // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { @@ -846,7 +915,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/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 @@ -856,32 +925,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - stats1 = add(stats1, newDefs.toList) + val addedStatsWithInitBitsAndChecks = implementWithNewDefs(statsWithInitChecks) - if (clazz.isTrait) stats1 = stats1.filter { - case vd: ValDef => - assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz") - false + if (clazz.isTrait) + addedStatsWithInitBitsAndChecks filter { + case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false case _ => true } - - if (!clazz.isTrait) stats1 = stats1 map completeSuperAccessor - - stats1 - } - - private def nullableFields(templ: Template): Map[Symbol, Set[Symbol]] = { - val scope = templ.symbol.owner.info.decls - // if there are no lazy fields, take the fast path and save a traversal of the whole AST - if (scope exists (_.isLazy)) { - val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() - // check what fields can be nulled for - for ((field, users) <- singleUseFields(templ); lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr)) - map(lazyFld) += field - - map.toMap + else { + addedStatsWithInitBitsAndChecks map completeSuperAccessor } - else Map() } /** The transform that gets applied to a tree after it has been completely @@ -902,10 +955,12 @@ 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() + val accessorInit = accessorInitialization(currentOwner, templ) + // add all new definitions to current class or interface - val statsWithNewDefs = addNewDefs(currentOwner, body) + val statsWithNewDefs = addNewDefs(currentOwner, body, accessorInit) statsWithNewDefs foreach { case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) => dd.symbol.updateAttachment(NeedStaticImpl) -- cgit v1.2.3 From ac133a1ce7409c87bf6e132a8fd263102f0d5a9e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 15 Aug 2016 17:50:12 -0700 Subject: [refactor] strictly reorder definitions --- src/compiler/scala/tools/nsc/transform/Mixin.scala | 1300 ++++++++++---------- 1 file changed, 646 insertions(+), 654 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 56c10218eb..55f344a5cc 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -21,15 +21,19 @@ trait InitBitmaps extends Transform with ast.TreeDSL { * For each class, fields defined by the class come after inherited fields. * Mixed-in fields count as fields defined by the class itself. */ - val fieldOffset = perRunCaches.newMap[Symbol, Int]() + private val fieldOffset = perRunCaches.newMap[Symbol, Int]() - val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() + private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() class InitializationTransformer extends Transformer { /** The typer */ protected var localTyper: erasure.Typer = _ protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) + def accessorInitialization(clazz: Symbol, templ: Template): AccessorInitialization = + if (settings.checkInit) new AddsCheckInitDefs(clazz, templ) + else new StandardAccessorInitialization(clazz, templ) + trait AccessorInitialization { protected val clazz: Symbol protected val templ: Template @@ -60,388 +64,285 @@ trait InitBitmaps extends Transform with ast.TreeDSL { private def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed) - def completeSuperAccessor(stat: Tree): Tree } - } -} + protected class StandardAccessorInitialization(val clazz: Symbol, val templ: Template) extends AccessorInitialization { + private val _newDefs = mutable.ListBuffer[Tree]() + /** Attribute given tree and anchor at given position */ + private def attributedDef(pos: Position, tree: Tree): Tree = { + debuglog("add new def to " + clazz + ": " + tree) + typedPos(pos)(tree) + } -abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { - import global._ - import definitions._ - import CODE._ + /** Add tree at given position as new definition */ + private def addDef(pos: Position, tree: ValOrDefDef): Unit = _newDefs += attributedDef(pos, 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 - /** The name of the phase: */ - val phaseName: String = "mixin" + /** 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)) - /** Some trait methods need to be implemented in subclasses, so they cannot be private. - * - * We used to publicize during explicitouter (for some reason), so the condition is a bit more involved now it's done here - * (need to exclude lambdaLIFTED methods, as they do no exist during explicitouter and thus did not need to be excluded...) - * - * They may be protected, now that traits are compiled 1:1 to interfaces. - * The same disclaimers about mapping Scala's notion of visibility to Java's apply: - * we cannot emit PROTECTED methods in interfaces on the JVM, - * but knowing that these trait methods are protected means we won't emit static forwarders. - * - * JVMLS: "Methods of interfaces may have any of the flags in Table 4.6-A set - * except ACC_PROTECTED, ACC_FINAL, ACC_SYNCHRONIZED, and ACC_NATIVE (JLS §9.4)." - * - * TODO: can we just set the right flags from the start?? - * could we use the final flag to indicate a private method is really-really-private? - */ - def publicizeTraitMethod(sym: Symbol): Unit = { - if ((sym hasFlag PRIVATE) && !(sym hasFlag LIFTED) && ( // lambdalifted methods can remain private - // super accessors by definition must be implemented in a subclass, so can't be private - // TODO: why are they ever private in a trait to begin with!?!? (could just name mangle them to begin with) - // TODO: can we add the SYNTHESIZE_IMPL_IN_SUBCLASS flag to super accessors symbols? - (sym hasFlag SUPERACCESSOR) - // an accessor / module *may* need to be implemented in a subclass, and thus cannot be private - // TODO: document how we get here (lambdalift? fields has already made accessors not-private) - || (sym hasFlag ACCESSOR | MODULE) && (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS))) - sym.makeNotPrivate(sym.owner) - // no need to make trait methods not-protected - // (we used to have to move them to another class when interfaces could not have concrete methods) - // see note in `synthFieldsAndAccessors` in Fields.scala - // if (sym hasFlag PROTECTED) sym setFlag notPROTECTED - } + // overridden in AddsCheckInitDefs + protected def addCheckedInitCheck(stat: DefDef): DefDef = stat - /** This map contains a binding (class -> info) if - * the class with this info at phase mixinPhase has been treated for mixin composition - */ - private val treatedClassInfos = perRunCaches.newMap[Symbol, Type]() withDefaultValue NoType + // overridden in AddsCheckInitDefs + protected def needsInitFlag(sym: Symbol): Boolean = false + // ByteClass, IntClass, LongClass + protected def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) -// --------- helper functions ----------------------------------------------- + protected def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr - /** A member of a trait is implemented statically if its implementation after the - * mixin transform is RHS of the method body (destined to be in a interface default method) - * - * To be statically implemented, a member must be a method that belonged to the trait's implementation class - * before (i.e. it is not abstract). Not statically implemented are - * - non-private modules: these are implemented directly in the mixin composition class - * (private modules, on the other hand, are implemented statically, but their - * module variable is not. all such private modules are lifted, because - * non-lifted private modules have been eliminated in ExplicitOuter) - * - field accessors and superaccessors, except for lazy value accessors which become initializer - * methods in the impl class (because they can have arbitrary initializers) - */ - private def isImplementedStatically(sym: Symbol) = ( - (sym.isMethod || ((sym hasFlag MODULE) && !sym.isStatic)) - && notDeferred(sym) - && sym.owner.isTrait - && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) - && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) - && !sym.isPrivate - && !sym.hasAllFlags(LIFTED | MODULE | METHOD) - && !sym.isConstructor - && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE)) - ) + /** 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(field: Symbol): Name = { + import nme._ + 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 + } + if (isFieldWithBitmap(field)) + if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL + else if (needsInitFlag(field) && !field.isDeferred) + if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + else NO_NAME + } - /** Returns the symbol that is accessed by a super-accessor in a mixin composition. - * - * @param base The class in which everything is mixed together - * @param member The symbol statically referred to by the superaccessor in the trait - * @param mixinClass The mixin class that produced the superaccessor - */ - private def rebindSuper(base: Symbol, member: Symbol, mixinClass: Symbol): Symbol = - exitingSpecialize { - var bcs = base.info.baseClasses.dropWhile(mixinClass != _).tail - var sym: Symbol = NoSymbol - debuglog("starting rebindsuper " + base + " " + member + ":" + member.tpe + - " " + mixinClass + " " + base.info.baseClasses + "/" + bcs) - while (!bcs.isEmpty && sym == NoSymbol) { - if (settings.debug) { - val other = bcs.head.info.nonPrivateDecl(member.name) - debuglog("rebindsuper " + bcs.head + " " + other + " " + other.tpe + - " " + other.isDeferred) - } - sym = member.matchingSymbol(bcs.head, base.thisType).suchThat(sym => !sym.hasFlag(DEFERRED | BRIDGE)) - bcs = bcs.tail + private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { + case BooleanClass => 1 + case ByteClass => 8 + case IntClass => 32 + case LongClass => 64 } - sym - } -// --------- type transformation ----------------------------------------------- + protected def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = { + def realOffset = offset % flagsPerBitmap(sym) + if (kind == LongClass) LIT(1L << realOffset) else LIT(1 << realOffset) + } - @inline final def notDeferred(sym: Symbol) = fields.notDeferredOrSynthImpl(sym) - /** Is member overridden (either directly or via a bridge) in base class sequence `bcs`? */ - def isOverriddenAccessor(member: Symbol, bcs: List[Symbol]): Boolean = beforeOwnPhase { - def hasOverridingAccessor(clazz: Symbol) = { - clazz.info.nonPrivateDecl(member.name).alternatives.exists( - sym => - sym.hasFlag(ACCESSOR) && - !sym.hasFlag(MIXEDIN) && - notDeferred(sym) && - matchesType(sym.tpe, member.tpe, alwaysMatchSimple = true)) - } - ( bcs.head != member.owner - && (hasOverridingAccessor(bcs.head) || isOverriddenAccessor(member, bcs.tail)) - ) - } + type WithInitChecks = List[Tree] + /** 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: WithInitChecks): 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) + } - /** Add given member to given class, and mark member as mixed-in. - */ - def addMember(clazz: Symbol, member: Symbol): Symbol = { - debuglog(s"mixing into $clazz: ${member.defString}") - // This attachment is used to instruct the backend about which methids in traits require - // a static trait impl method. We remove this from the new symbol created for the method - // mixed into the subclass. - member.removeAttachment[NeedStaticImpl.type] - clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD - } - def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = - addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz)) + protected def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass - def cloneBeforeErasure(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = { - val newSym = enteringErasure { - // since we used `mixinMember` from the interface that represents the trait that's - // being mixed in, have to instantiate the interface type params (that may occur in mixinMember's - // info) as they are seen from the class. We can't use the member that we get from the - // implementation class, as it's a clone that was made after erasure, and thus it does not - // know its info at the beginning of erasure anymore. - val sym = mixinMember cloneSymbol clazz + def addInitChecks(stats: List[Tree]): WithInitChecks = { + /* 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: Unit = { + def fold(fields: List[Symbol], category: Name) = { + var idx = 0 + fields foreach { f => + fieldOffset(f) = idx + idx += 1 + } - val erasureMap = erasure.erasure(mixinMember) - val erasedInterfaceInfo: Type = erasureMap(mixinMember.info) - val specificForwardInfo = (clazz.thisType baseType mixinClass) memberInfo mixinMember - val forwarderInfo = - if (erasureMap(specificForwardInfo) =:= erasedInterfaceInfo) - specificForwardInfo - else { - erasedInterfaceInfo + 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) + } } - // Optimize: no need if mixinClass has no typeparams. - // !!! JZ Really? What about the effect of abstract types, prefix? - if (mixinClass.typeParams.isEmpty) sym - else sym modifyInfo (_ => forwarderInfo) - } - newSym - } - def publicizeTraitMethods(clazz: Symbol) { - if (treatedClassInfos(clazz) != clazz.info) { - treatedClassInfos(clazz) = clazz.info - assert(phase == currentRun.mixinPhase, phase) + /** 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 addInitCheck(stat: DefDef): DefDef = { + val sym = stat.symbol - for (member <- clazz.info.decls) { - if (member.isMethod) publicizeTraitMethod(member) - 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 - clazz.info.decls.unlink(member) - } - - } - debuglog("new defs of " + clazz + " = " + clazz.info.decls) - } - } - - /** Add all members to be mixed in into a (non-trait-) class - * These are: - * for every mixin trait T that is not also inherited by the superclass: - * add late interface members to T and then: - * - if a member M of T is forwarded to the implementation class, add - * a forwarder for M unless one exists already. - * The alias of the forwarder is the static member it forwards to. - * - for every abstract accessor in T, add a field and an implementation for that accessor - * - for every super accessor in T, add an implementation of that accessor - * - for every module in T, add a module - */ - def addMixedinMembers(clazz: Symbol, unit: CompilationUnit) { - def cloneAndAddMixinMember(mixinClass: Symbol, mixinMember: Symbol): Symbol = ( - cloneAndAddMember(mixinClass, mixinMember, clazz) - setPos clazz.pos - resetFlag DEFERRED - ) - - /* Mix in members of implementation class mixinClass into class clazz */ - def mixinTraitForwarders(mixinClass: Symbol) { - for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) { - member overridingSymbol clazz match { - case NoSymbol => - val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) - if (isMemberOfClazz) { - def genForwarder(): Unit = { - cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member - } - - if (settings.XgenMixinForwarders) genForwarder() - else { - - // `member` is a concrete method defined in `mixinClass`, which is a base class of - // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: - // - // - A non-trait base class of `clazz` defines a matching method. Example: - // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T - // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would - // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. - // - // - There exists another concrete, matching method in a parent interface `p` of - // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the - // forwarder is needed to disambiguate. Example: - // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 - // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves - // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". - // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 - // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM - // level. - - @tailrec - def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { - case baseClass :: rest => - if (baseClass ne mixinClass) { - val m = member.overriddenSymbol(baseClass) - val isCompeting = m.exists && { - !m.owner.isTraitOrInterface || - (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) - } - isCompeting || existsCompetingMethod(rest) - } else existsCompetingMethod(rest) + if (!clazz.isTrait && sym.isLazy && stat.rhs != EmptyTree) { + assert(fieldOffset contains sym, sym) + deriveDefDef(stat) { + case t if isUnitGetter(sym) => + mkLazyMemberDef(sym, List(t), UNIT, fieldOffset(sym)) - case _ => false - } + case Block(stats, res) => + mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) - if (existsCompetingMethod(clazz.baseClasses)) - genForwarder() - else if (!settings.nowarnDefaultJunitMethods && JUnitTestClass.exists && member.hasAnnotation(JUnitTestClass)) - warning(member.pos, "JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.") - } + case t => t // pass specialized lazy vals through } + } + else addCheckedInitCheck(stat) + } - case _ => + buildBitmapOffsets + + stats mapConserve { + case dd: DefDef => addInitCheck(dd) + case stat => stat } } - } - /* Mix in members of trait mixinClass into class clazz. - */ - def mixinTraitMembers(mixinClass: Symbol) { - // For all members of a trait's interface do: - for (mixinMember <- mixinClass.info.decls) { - if (mixinMember.hasFlag(SUPERACCESSOR)) { // mixin super accessors - val superAccessor = addMember(clazz, mixinMember.cloneSymbol(clazz)) setPos clazz.pos - assert(superAccessor.alias != NoSymbol, superAccessor) - - rebindSuper(clazz, mixinMember.alias, mixinClass) match { - case NoSymbol => - reporter.error(clazz.pos, "Member %s of mixin %s is missing a concrete super implementation.".format( - mixinMember.alias, mixinClass)) - case alias1 => - if (alias1.owner.isJavaDefined && alias1.owner.isInterface && !clazz.parentSymbols.contains(alias1.owner)) { - val suggestedParent = exitingTyper(clazz.info.baseType(alias1.owner)) - reporter.error(clazz.pos, s"Unable to implement a super accessor required by trait ${mixinClass.name} unless $suggestedParent is directly extended by $clazz.") - } - superAccessor.asInstanceOf[TermSymbol] setAlias alias1 - } + /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the + * precise comparison operator depending on the value of 'equalToZero'. + */ + protected def mkTest(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) } - else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember) - && (mixinMember hasFlag PARAMACCESSOR) - && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { - // mixin accessor for constructor parameter - // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) - cloneAndAddMixinMember(mixinClass, mixinMember) + } - val name = mixinMember.name + /* + * 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(offset: RunId, field: global.Symbol): Symbol = { + val category = bitmapCategory(field) + val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName + val sym = clazz.info.decl(bitmapName) - if (!nme.isSetterName(name)) { - // enteringPhase: the private field is moved to the implementation class by erasure, - // so it can no longer be found in the mixinMember's owner (the trait) - val accessed = enteringPickler(mixinMember.accessed) - // #3857, need to retain info before erasure when cloning (since cloning only - // carries over the current entry in the type history) - val sym = enteringErasure { - // so we have a type history entry before erasure - clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType) - } - sym updateInfo mixinMember.tpe.resultType // info at current phase + assert(!sym.isOverloaded, sym) - val newFlags = ( - (PrivateLocal) - | (mixinMember getFlag MUTABLE) - | (if (mixinMember.hasStableFlag) 0 else MUTABLE) - ) + def createBitmap: Symbol = { + val bitmapKind = bitmapKindForCategory(category) + val sym = clazz.newVariable(bitmapName, clazz.pos) setInfo bitmapKind.tpe + enteringTyper(sym addAnnotation VolatileAttr) - addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations) + 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 + clazz.info.decls.enter(sym) + addDef(clazz.pos, init) + sym } + + sym orElse createBitmap } - } - if (clazz.isJavaDefined || treatedClassInfos(clazz) == clazz.info) - return + /* Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ + def mkSetFlag(offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { + val bmp = bitmapFor(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)) - treatedClassInfos(clazz) = clazz.info - assert(!clazz.isTrait && clazz.info.parents.nonEmpty, clazz) + x === newValue + } - // first complete the superclass with mixed in members - addMixedinMembers(clazz.superClass, unit) - for (mc <- clazz.mixinClasses ; if mc.isTrait) { - // @SEAN: adding trait tracking so we don't have to recompile transitive closures - unit.depends += mc - publicizeTraitMethods(mc) - mixinTraitMembers(mc) - mixinTraitForwarders(mc) - } - } + /** 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 lzyVal The symbol of this lazy field + * @param init The tree which initializes the field ( f = ) + * @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$ = + * 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. + */ + private def mkLazyMemberDef(lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = { + def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) - override def transformInfo(sym: Symbol, tp: Type): Type = tp + val bitmapSym = bitmapFor(offset, lzyVal) + val kind = bitmapKind(lzyVal) + val mask = maskForOffset(offset, lzyVal, kind) + val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify -// --------- term transformation ----------------------------------------------- + if (nulls.nonEmpty) + log("nulling fields inside " + lzyVal + ": " + nulls) - protected def newTransformer(unit: CompilationUnit): Transformer = - new MixinTransformer(unit) + val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? + val slowPathSym = + clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) - class MixinTransformer(unit : CompilationUnit) extends InitializationTransformer { - - /** The rootContext used for typing */ - private val rootContext = - erasure.NoContext.make(EmptyTree, rootMirror.RootClass, newScope) - - - /** The first transform; called in a pre-order traversal at phase mixin - * (that is, every node is processed before its children). - * What transform does: - * - For every non-trait class, add all mixed in members to the class info. - */ - private def preTransform(tree: Tree): Tree = { - val sym = tree.symbol - tree match { - case Template(parents, self, body) => - localTyper = erasure.newTyper(rootContext.make(tree, currentOwner)) - exitingMixin(currentOwner.owner.info)//todo: needed? - - if (!currentOwner.isTrait && !isPrimitiveValueClass(currentOwner)) - addMixedinMembers(currentOwner, unit) - else if (currentOwner.isTrait) - publicizeTraitMethods(currentOwner) + def thisRef = gen.mkAttributedThis(clazz) + def cond = mkTest(mask, bitmapSym, equalToZero = true, kind) - tree + val statsToSynch = init ::: List(mkSetFlag(offset, lzyVal, kind), UNIT) + val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) + addDef(pos, DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(currentOwner -> slowPathSym)), retVal))) - case _ => tree + typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathSym), Nil), retVal)) } - } - def accessorInitialization(clazz: Symbol, templ: Template): AccessorInitialization = - if (settings.checkInit) new AddsCheckInitDefs(clazz, templ) - else new StandardAccessorInitialization(clazz, templ) - - - protected class StandardAccessorInitialization(val clazz: Symbol, val templ: Template) extends AccessorInitialization { /** Map lazy values to the fields they should null after initialization. */ private lazy val lazyValNullables: Map[Symbol, List[Symbol]] = - // if there are no lazy fields, take the fast path and save a traversal of the whole AST + // 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. @@ -489,399 +390,473 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { map.mapValues(_.toList sortBy (_.id)).toMap } + } - // overridden in AddsCheckInitDefs - protected def addCheckedInitCheck(stat: DefDef): DefDef = stat - - // ByteClass, IntClass, LongClass - protected def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) - /** 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 class AddsCheckInitDefs(clazz: Symbol, templ: Template) extends StandardAccessorInitialization(clazz, templ) { + /** 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 bitmapCategory(field: Symbol): Name = { - import nme._ + override 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) + ) - 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 + object addInitBitsTransformer extends Transformer { + private def checkedGetter(lhs: Tree) = { + val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) + if (needsInitFlag(sym) && (fieldOffset contains sym)) { + debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) + List(localTyper typed mkSetFlag(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 -- 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 + ) + } + } - if (isFieldWithBitmap(field)) - if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL - else if (needsInitFlag(field) && !field.isDeferred) - if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT - else NO_NAME + /** Adds statements to set the 'init' bit for each field initialized + * in the body of a constructor. + * @pre settings.checkInit + */ + def addInitBits(rhs: Tree): Tree = addInitBitsTransformer transform rhs + + override def addCheckedInitCheck(stat: DefDef): DefDef = { + val sym = stat.symbol + if (needsInitFlag(sym) && stat.rhs != EmptyTree && !clazz.hasFlag(TRAIT)) { + assert(fieldOffset contains sym, sym) + deriveDefDef(stat)(rhs => + (mkCheckedAccessor(_: Tree, fieldOffset(sym), stat.pos, sym)) ( + if (isUnitGetter(sym)) UNIT else rhs + ) + ) + } + else if (sym.isConstructor) { + deriveDefDef(stat)(addInitBits) + } + else if (!clazz.isTrait && sym.isSetter) { + val getter = sym.getterIn(clazz) + if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) + deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) + else stat + } + else stat } - private val _newDefs = mutable.ListBuffer[Tree]() + def mkCheckedAccessor(retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { + val sym = fieldSym.getterIn(fieldSym.owner) + val bitmapSym = bitmapFor(offset, sym) + val kind = bitmapKind(sym) + val mask = maskForOffset(offset, sym, kind) + val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" + val result = + IF(mkTest(mask, bitmapSym, equalToZero = false, kind)). + THEN(retVal). + ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) - /** Attribute given tree and anchor at given position */ - private def attributedDef(pos: Position, tree: Tree): Tree = { - debuglog("add new def to " + clazz + ": " + tree) - typedPos(pos)(tree) + typedPos(pos)(BLOCK(result, retVal)) } + // TODO: need to run this on all accessor bodies (after fields refactoring, this is only run on accessors mixed in during mixins, which is only PRESUPER | PARAMACCESSOR) + override def getterBody(getter: Symbol) = { + if (!needsInitFlag(getter)) super.getterBody(getter) + else mkCheckedAccessor(super.getterBody(getter), fieldOffset(getter), getter.pos, getter) + } - /** 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 + override def setterBody(setter: Symbol, getter: Symbol) = { + if (!needsInitFlag(getter)) super.setterBody(setter, getter) + else Block(List(super.setterBody(setter, getter)), mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter))) + } + } + } +} - /** Add tree at given position as new definition */ - private def addDef(pos: Position, tree: ValOrDefDef): Unit = - _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)) +abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { + import global._ + import definitions._ + import CODE._ + /** The name of the phase: */ + val phaseName: String = "mixin" - protected def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr + /** Some trait methods need to be implemented in subclasses, so they cannot be private. + * + * We used to publicize during explicitouter (for some reason), so the condition is a bit more involved now it's done here + * (need to exclude lambdaLIFTED methods, as they do no exist during explicitouter and thus did not need to be excluded...) + * + * They may be protected, now that traits are compiled 1:1 to interfaces. + * The same disclaimers about mapping Scala's notion of visibility to Java's apply: + * we cannot emit PROTECTED methods in interfaces on the JVM, + * but knowing that these trait methods are protected means we won't emit static forwarders. + * + * JVMLS: "Methods of interfaces may have any of the flags in Table 4.6-A set + * except ACC_PROTECTED, ACC_FINAL, ACC_SYNCHRONIZED, and ACC_NATIVE (JLS §9.4)." + * + * TODO: can we just set the right flags from the start?? + * could we use the final flag to indicate a private method is really-really-private? + */ + def publicizeTraitMethod(sym: Symbol): Unit = { + if ((sym hasFlag PRIVATE) && !(sym hasFlag LIFTED) && ( // lambdalifted methods can remain private + // super accessors by definition must be implemented in a subclass, so can't be private + // TODO: why are they ever private in a trait to begin with!?!? (could just name mangle them to begin with) + // TODO: can we add the SYNTHESIZE_IMPL_IN_SUBCLASS flag to super accessors symbols? + (sym hasFlag SUPERACCESSOR) + // an accessor / module *may* need to be implemented in a subclass, and thus cannot be private + // TODO: document how we get here (lambdalift? fields has already made accessors not-private) + || (sym hasFlag ACCESSOR | MODULE) && (sym hasFlag SYNTHESIZE_IMPL_IN_SUBCLASS))) + sym.makeNotPrivate(sym.owner) - // overridden in AddsCheckInitDefs - protected def needsInitFlag(sym: Symbol): Boolean = false + // no need to make trait methods not-protected + // (we used to have to move them to another class when interfaces could not have concrete methods) + // see note in `synthFieldsAndAccessors` in Fields.scala + // if (sym hasFlag PROTECTED) sym setFlag notPROTECTED + } - protected def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + /** This map contains a binding (class -> info) if + * the class with this info at phase mixinPhase has been treated for mixin composition + */ + private val treatedClassInfos = perRunCaches.newMap[Symbol, Type]() withDefaultValue NoType - private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { - case BooleanClass => 1 - case ByteClass => 8 - case IntClass => 32 - case LongClass => 64 - } +// --------- helper functions ----------------------------------------------- - type WithInitChecks = List[Tree] + /** A member of a trait is implemented statically if its implementation after the + * mixin transform is RHS of the method body (destined to be in a interface default method) + * + * To be statically implemented, a member must be a method that belonged to the trait's implementation class + * before (i.e. it is not abstract). Not statically implemented are + * - non-private modules: these are implemented directly in the mixin composition class + * (private modules, on the other hand, are implemented statically, but their + * module variable is not. all such private modules are lifted, because + * non-lifted private modules have been eliminated in ExplicitOuter) + * - field accessors and superaccessors, except for lazy value accessors which become initializer + * methods in the impl class (because they can have arbitrary initializers) + */ + private def isImplementedStatically(sym: Symbol) = ( + (sym.isMethod || ((sym hasFlag MODULE) && !sym.isStatic)) + && notDeferred(sym) + && sym.owner.isTrait + && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) + && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) + && !sym.isPrivate + && !sym.hasAllFlags(LIFTED | MODULE | METHOD) + && !sym.isConstructor + && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE)) + ) - /** 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: WithInitChecks): 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 + + + /** Returns the symbol that is accessed by a super-accessor in a mixin composition. + * + * @param base The class in which everything is mixed together + * @param member The symbol statically referred to by the superaccessor in the trait + * @param mixinClass The mixin class that produced the superaccessor + */ + private def rebindSuper(base: Symbol, member: Symbol, mixinClass: Symbol): Symbol = + exitingSpecialize { + var bcs = base.info.baseClasses.dropWhile(mixinClass != _).tail + var sym: Symbol = NoSymbol + debuglog("starting rebindsuper " + base + " " + member + ":" + member.tpe + + " " + mixinClass + " " + base.info.baseClasses + "/" + bcs) + while (!bcs.isEmpty && sym == NoSymbol) { + if (settings.debug) { + val other = bcs.head.info.nonPrivateDecl(member.name) + debuglog("rebindsuper " + bcs.head + " " + other + " " + other.tpe + + " " + other.isDeferred) } - if (newDefs.isEmpty) stats - else newDefs ::: (stats filter isNotDuplicate) + sym = member.matchingSymbol(bcs.head, base.thisType).suchThat(sym => !sym.hasFlag(DEFERRED | BRIDGE)) + bcs = bcs.tail } + sym + } - /** 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 +// --------- type transformation ----------------------------------------------- - copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt)))) - case _ => - stat - } + @inline final def notDeferred(sym: Symbol) = fields.notDeferredOrSynthImpl(sym) - /* - * 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(offset: RunId, field: global.Symbol): Symbol = { - val category = bitmapCategory(field) - val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName - val sym = clazz.info.decl(bitmapName) + /** Is member overridden (either directly or via a bridge) in base class sequence `bcs`? */ + def isOverriddenAccessor(member: Symbol, bcs: List[Symbol]): Boolean = beforeOwnPhase { + def hasOverridingAccessor(clazz: Symbol) = { + clazz.info.nonPrivateDecl(member.name).alternatives.exists( + sym => + sym.hasFlag(ACCESSOR) && + !sym.hasFlag(MIXEDIN) && + notDeferred(sym) && + matchesType(sym.tpe, member.tpe, alwaysMatchSimple = true)) + } + ( bcs.head != member.owner + && (hasOverridingAccessor(bcs.head) || isOverriddenAccessor(member, bcs.tail)) + ) + } - assert(!sym.isOverloaded, sym) - def createBitmap: Symbol = { - val bitmapKind = bitmapKindForCategory(category) - val sym = clazz.newVariable(bitmapName, clazz.pos) setInfo bitmapKind.tpe - enteringTyper(sym addAnnotation VolatileAttr) + /** Add given member to given class, and mark member as mixed-in. + */ + def addMember(clazz: Symbol, member: Symbol): Symbol = { + debuglog(s"mixing into $clazz: ${member.defString}") + // This attachment is used to instruct the backend about which methids in traits require + // a static trait impl method. We remove this from the new symbol created for the method + // mixed into the subclass. + member.removeAttachment[NeedStaticImpl.type] + clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD + } + def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = + addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz)) - 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) - } + def cloneBeforeErasure(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = { + val newSym = enteringErasure { + // since we used `mixinMember` from the interface that represents the trait that's + // being mixed in, have to instantiate the interface type params (that may occur in mixinMember's + // info) as they are seen from the class. We can't use the member that we get from the + // implementation class, as it's a clone that was made after erasure, and thus it does not + // know its info at the beginning of erasure anymore. + val sym = mixinMember cloneSymbol clazz - sym setFlag PrivateLocal - clazz.info.decls.enter(sym) - addDef(clazz.pos, init) - sym + val erasureMap = erasure.erasure(mixinMember) + val erasedInterfaceInfo: Type = erasureMap(mixinMember.info) + val specificForwardInfo = (clazz.thisType baseType mixinClass) memberInfo mixinMember + val forwarderInfo = + if (erasureMap(specificForwardInfo) =:= erasedInterfaceInfo) + specificForwardInfo + else { + erasedInterfaceInfo } + // Optimize: no need if mixinClass has no typeparams. + // !!! JZ Really? What about the effect of abstract types, prefix? + if (mixinClass.typeParams.isEmpty) sym + else sym modifyInfo (_ => forwarderInfo) + } + newSym + } - sym orElse createBitmap - } - - protected 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(offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { - val bmp = bitmapFor(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 - } + def publicizeTraitMethods(clazz: Symbol) { + if (treatedClassInfos(clazz) != clazz.info) { + treatedClassInfos(clazz) = clazz.info + assert(phase == currentRun.mixinPhase, phase) - /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the - * precise comparison operator depending on the value of 'equalToZero'. - */ - protected def mkTest(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) + for (member <- clazz.info.decls) { + if (member.isMethod) publicizeTraitMethod(member) + 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 + clazz.info.decls.unlink(member) } + } + debuglog("new defs of " + clazz + " = " + clazz.info.decls) + } + } + /** Add all members to be mixed in into a (non-trait-) class + * These are: + * for every mixin trait T that is not also inherited by the superclass: + * add late interface members to T and then: + * - if a member M of T is forwarded to the implementation class, add + * a forwarder for M unless one exists already. + * The alias of the forwarder is the static member it forwards to. + * - for every abstract accessor in T, add a field and an implementation for that accessor + * - for every super accessor in T, add an implementation of that accessor + * - for every module in T, add a module + */ + def addMixedinMembers(clazz: Symbol, unit: CompilationUnit) { + def cloneAndAddMixinMember(mixinClass: Symbol, mixinMember: Symbol): Symbol = ( + cloneAndAddMember(mixinClass, mixinMember, clazz) + setPos clazz.pos + resetFlag DEFERRED + ) - /** 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 lzyVal The symbol of this lazy field - * @param init The tree which initializes the field ( f = ) - * @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$ = - * 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. - */ - private def mkLazyMemberDef(lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = { - def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) + /* Mix in members of implementation class mixinClass into class clazz */ + def mixinTraitForwarders(mixinClass: Symbol) { + for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) { + member overridingSymbol clazz match { + case NoSymbol => + val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) + if (isMemberOfClazz) { + def genForwarder(): Unit = { + cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + } - val bitmapSym = bitmapFor(offset, lzyVal) - val kind = bitmapKind(lzyVal) - val mask = maskForOffset(offset, lzyVal, kind) - val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify + if (settings.XgenMixinForwarders) genForwarder() + else { - if (nulls.nonEmpty) - log("nulling fields inside " + lzyVal + ": " + nulls) + // `member` is a concrete method defined in `mixinClass`, which is a base class of + // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: + // + // - A non-trait base class of `clazz` defines a matching method. Example: + // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T + // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would + // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. + // + // - There exists another concrete, matching method in a parent interface `p` of + // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the + // forwarder is needed to disambiguate. Example: + // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 + // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves + // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". + // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 + // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM + // level. - val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? - val slowPathSym = - clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) + @tailrec + def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { + case baseClass :: rest => + if (baseClass ne mixinClass) { + val m = member.overriddenSymbol(baseClass) + val isCompeting = m.exists && { + !m.owner.isTraitOrInterface || + (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) + } + isCompeting || existsCompetingMethod(rest) + } else existsCompetingMethod(rest) - def thisRef = gen.mkAttributedThis(clazz) - def cond = mkTest(mask, bitmapSym, equalToZero = true, kind) + case _ => false + } - val statsToSynch = init ::: List(mkSetFlag(offset, lzyVal, kind), UNIT) - val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) - addDef(pos, DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(currentOwner -> slowPathSym)), retVal))) + if (existsCompetingMethod(clazz.baseClasses)) + genForwarder() + else if (!settings.nowarnDefaultJunitMethods && JUnitTestClass.exists && member.hasAnnotation(JUnitTestClass)) + warning(member.pos, "JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.") + } + } - typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathSym), Nil), retVal)) + case _ => + } } + } + /* Mix in members of trait mixinClass into class clazz. + */ + def mixinTraitMembers(mixinClass: Symbol) { + // For all members of a trait's interface do: + for (mixinMember <- mixinClass.info.decls) { + if (mixinMember.hasFlag(SUPERACCESSOR)) { // mixin super accessors + val superAccessor = addMember(clazz, mixinMember.cloneSymbol(clazz)) setPos clazz.pos + assert(superAccessor.alias != NoSymbol, superAccessor) - def addInitChecks(stats: List[Tree]): WithInitChecks = { - /* 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: Unit = { - 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) + rebindSuper(clazz, mixinMember.alias, mixinClass) match { + case NoSymbol => + reporter.error(clazz.pos, "Member %s of mixin %s is missing a concrete super implementation.".format( + mixinMember.alias, mixinClass)) + case alias1 => + if (alias1.owner.isJavaDefined && alias1.owner.isInterface && !clazz.parentSymbols.contains(alias1.owner)) { + val suggestedParent = exitingTyper(clazz.info.baseType(alias1.owner)) + reporter.error(clazz.pos, s"Unable to implement a super accessor required by trait ${mixinClass.name} unless $suggestedParent is directly extended by $clazz.") + } + superAccessor.asInstanceOf[TermSymbol] setAlias alias1 } } + else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember) + && (mixinMember hasFlag PARAMACCESSOR) + && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { + // mixin accessor for constructor parameter + // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) + cloneAndAddMixinMember(mixinClass, mixinMember) - /** 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 addInitCheck(stat: DefDef): DefDef = { - val sym = stat.symbol + val name = mixinMember.name - if (!clazz.isTrait && sym.isLazy && stat.rhs != EmptyTree) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnitGetter(sym) => - mkLazyMemberDef(sym, List(t), UNIT, fieldOffset(sym)) + if (!nme.isSetterName(name)) { + // enteringPhase: the private field is moved to the implementation class by erasure, + // so it can no longer be found in the mixinMember's owner (the trait) + val accessed = enteringPickler(mixinMember.accessed) + // #3857, need to retain info before erasure when cloning (since cloning only + // carries over the current entry in the type history) + val sym = enteringErasure { + // so we have a type history entry before erasure + clazz.newValue(mixinMember.localName, mixinMember.pos).setInfo(mixinMember.tpe.resultType) + } + sym updateInfo mixinMember.tpe.resultType // info at current phase - case Block(stats, res) => - mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) + val newFlags = ( + (PrivateLocal) + | (mixinMember getFlag MUTABLE) + | (if (mixinMember.hasStableFlag) 0 else MUTABLE) + ) - case t => t // pass specialized lazy vals through - } + addMember(clazz, sym setFlag newFlags setAnnotations accessed.annotations) } - else addCheckedInitCheck(stat) } + } + } - buildBitmapOffsets + if (clazz.isJavaDefined || treatedClassInfos(clazz) == clazz.info) + return - stats mapConserve { - case dd: DefDef => addInitCheck(dd) - case stat => stat - } - } + treatedClassInfos(clazz) = clazz.info + assert(!clazz.isTrait && clazz.info.parents.nonEmpty, clazz) + + // first complete the superclass with mixed in members + addMixedinMembers(clazz.superClass, unit) + for (mc <- clazz.mixinClasses ; if mc.isTrait) { + // @SEAN: adding trait tracking so we don't have to recompile transitive closures + unit.depends += mc + publicizeTraitMethods(mc) + mixinTraitMembers(mc) + mixinTraitForwarders(mc) } + } - protected class AddsCheckInitDefs(clazz: Symbol, templ: Template) extends StandardAccessorInitialization(clazz, templ) { - /** 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. - */ - override 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) - ) + override def transformInfo(sym: Symbol, tp: Type): Type = tp - object addInitBitsTransformer extends Transformer { - private def checkedGetter(lhs: Tree) = { - val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitFlag(sym) && (fieldOffset contains sym)) { - debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(localTyper typed mkSetFlag(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 -- 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 - ) - } - } +// --------- term transformation ----------------------------------------------- - /** Adds statements to set the 'init' bit for each field initialized - * in the body of a constructor. - * @pre settings.checkInit - */ - def addInitBits(rhs: Tree): Tree = addInitBitsTransformer transform rhs + protected def newTransformer(unit: CompilationUnit): Transformer = + new MixinTransformer(unit) - override def addCheckedInitCheck(stat: DefDef): DefDef = { - val sym = stat.symbol - if (needsInitFlag(sym) && stat.rhs != EmptyTree && !clazz.hasFlag(TRAIT)) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat)(rhs => - (mkCheckedAccessor(_: Tree, fieldOffset(sym), stat.pos, sym)) ( - if (isUnitGetter(sym)) UNIT else rhs - ) - ) - } - else if (sym.isConstructor) { - deriveDefDef(stat)(addInitBits) - } - else if (!clazz.isTrait && sym.isSetter) { - val getter = sym.getterIn(clazz) - if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) - deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) - else stat - } - else stat - } + class MixinTransformer(unit : CompilationUnit) extends InitializationTransformer { - def mkCheckedAccessor(retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { - val sym = fieldSym.getterIn(fieldSym.owner) - val bitmapSym = bitmapFor(offset, sym) - val kind = bitmapKind(sym) - val mask = maskForOffset(offset, sym, kind) - val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" - val result = - IF(mkTest(mask, bitmapSym, equalToZero = false, kind)). - THEN(retVal). - ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) + /** The rootContext used for typing */ + private val rootContext = + erasure.NoContext.make(EmptyTree, rootMirror.RootClass, newScope) - typedPos(pos)(BLOCK(result, retVal)) - } - // TODO: need to run this on all accessor bodies (after fields refactoring, this is only run on accessors mixed in during mixins, which is only PRESUPER | PARAMACCESSOR) - override def getterBody(getter: Symbol) = { - if (!needsInitFlag(getter)) super.getterBody(getter) - else mkCheckedAccessor(super.getterBody(getter), fieldOffset(getter), getter.pos, getter) - } + /** The first transform; called in a pre-order traversal at phase mixin + * (that is, every node is processed before its children). + * What transform does: + * - For every non-trait class, add all mixed in members to the class info. + */ + private def preTransform(tree: Tree): Tree = { + val sym = tree.symbol + tree match { + case Template(parents, self, body) => + localTyper = erasure.newTyper(rootContext.make(tree, currentOwner)) + exitingMixin(currentOwner.owner.info)//todo: needed? - override def setterBody(setter: Symbol, getter: Symbol) = { - if (!needsInitFlag(getter)) super.setterBody(setter, getter) - else Block(List(super.setterBody(setter, getter)), mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter))) + if (!currentOwner.isTrait && !isPrimitiveValueClass(currentOwner)) + addMixedinMembers(currentOwner, unit) + else if (currentOwner.isTrait) + publicizeTraitMethods(currentOwner) + + tree + + case _ => tree } } @@ -933,6 +908,23 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { 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 + } + addedStatsWithInitBitsAndChecks map completeSuperAccessor } } -- cgit v1.2.3 From c3b5f1bab3fc65296fcc596f3378ff940b4fb92c Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 16 Aug 2016 11:29:00 -0700 Subject: Precompute bitmap info for lazy/init-checked vals Instead of doing this lazily, rework the logic to make it suitable for operating first on symbols (during the info transform), then on trees (tree transform). --- src/compiler/scala/tools/nsc/transform/Mixin.scala | 511 +++++++++++---------- 1 file changed, 256 insertions(+), 255 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 55f344a5cc..b3558d6281 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -20,30 +20,58 @@ trait InitBitmaps extends Transform with ast.TreeDSL { /** 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. + * + * DO NOT USE THIS MAP DIRECTLY, go through convenience methods */ - private val fieldOffset = perRunCaches.newMap[Symbol, Int]() + private val _fieldOffset = perRunCaches.newMap[Symbol, Int]() private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() - class InitializationTransformer extends Transformer { - /** The typer */ - protected var localTyper: erasure.Typer = _ - protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) + trait InitializationTransformation { + protected def typedPos(pos: Position)(tree: Tree): Tree - def accessorInitialization(clazz: Symbol, templ: Template): AccessorInitialization = - if (settings.checkInit) new AddsCheckInitDefs(clazz, templ) - else new StandardAccessorInitialization(clazz, templ) + def accessorInitialization(clazz: Symbol, templStats: List[Tree]): AccessorInitialization = + if (settings.checkInit) new CheckInitAccessorInitialization(clazz, templStats) + else new LazyAccessorInitialization(clazz, templStats) - trait AccessorInitialization { - protected val clazz: Symbol - protected val templ: Template + class AccessorInitialization(protected val clazz: Symbol){ + private val _newDefs = mutable.ListBuffer[Tree]() - def addDefDef(sym: Symbol, rhs: Tree = EmptyTree): Unit - def addValDef(sym: Symbol, rhs: Tree = EmptyTree): Unit + def deriveStatsWithInitChecks(stats: List[Tree]): List[Tree] = stats - type WithInitChecks - def addInitChecks(stats: List[Tree]): WithInitChecks - def implementWithNewDefs(stats: WithInitChecks): List[Tree] + /** 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) @@ -66,42 +94,27 @@ trait InitBitmaps extends Transform with ast.TreeDSL { } - protected class StandardAccessorInitialization(val clazz: Symbol, val templ: Template) extends AccessorInitialization { - private val _newDefs = mutable.ListBuffer[Tree]() - - /** Attribute given tree and anchor at given position */ - private def attributedDef(pos: Position, tree: Tree): Tree = { - debuglog("add new def to " + clazz + ": " + tree) - typedPos(pos)(tree) - } - /** Add tree at given position as new definition */ - private def addDef(pos: Position, tree: ValOrDefDef): Unit = _newDefs += attributedDef(pos, tree) + protected class LazyAccessorInitialization(clazz: Symbol, templStats: List[Tree]) extends AccessorInitialization(clazz) { + private def bitmapNumber(sym: Symbol): Int = _fieldOffset(sym) / flagsPerBitmap(sym) + private def offsetInBitmap(sym: Symbol) = _fieldOffset(sym) % flagsPerBitmap(sym) + protected def hasBitmap(sym: Symbol) = _fieldOffset isDefinedAt sym - /** 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(position(sym), DefDef(sym, rhs)) - def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), ValDef(sym, rhs)) - - - // overridden in AddsCheckInitDefs - protected def addCheckedInitCheck(stat: DefDef): DefDef = stat - - // overridden in AddsCheckInitDefs - protected def needsInitFlag(sym: Symbol): Boolean = false + private def flagsPerBitmap(field: Symbol): Int = + bitmapKind(field) match { + case BooleanClass => 1 + case ByteClass => 8 + case IntClass => 32 + case LongClass => 64 + } - // ByteClass, IntClass, LongClass protected def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) + protected def bitmapType(field: Symbol): Type = bitmapKind(field).tpe + protected def bitmapName(field: Symbol): TermName = nme.newBitmapName(bitmapCategory(field), bitmapNumber(field)).toTermName protected def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr +// protected def isFieldWithTransientBitmap(field: Symbol) = isTransientField(field) && isFieldWithBitmap(field) + /** Examines the symbol and returns a name indicating what brand of * bitmap it requires. The possibilities are the BITMAP_* vals @@ -110,8 +123,6 @@ trait InitBitmaps extends Transform with ast.TreeDSL { * bitmaps for checkinit fields are not inherited */ protected def bitmapCategory(field: Symbol): Name = { - import nme._ - def isFieldWithBitmap(field: Symbol) = { field.info // ensure that nested objects are transformed // For checkinit consider normal value getters @@ -119,163 +130,142 @@ trait InitBitmaps extends Transform with ast.TreeDSL { field.isLazy && field.isMethod && !field.isDeferred } + import nme._ + if (isFieldWithBitmap(field)) if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL - else if (needsInitFlag(field) && !field.isDeferred) - if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT else NO_NAME } - private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { - case BooleanClass => 1 - case ByteClass => 8 - case IntClass => 32 - case LongClass => 64 + /** Fill the map from fields to offset numbers. And from bitmap category to the type used for the bitmap field (its "kind"). + * + * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later. + */ + def bitmapsFor(decls: List[Symbol]): List[Symbol] = { +// val bitmaps = mutable.ListBuffer.empty[Symbol] + def fold(fields: List[Symbol], category: Name) = { + var idx = 0 + fields foreach { f => + _fieldOffset(f) = idx +// bitmaps += newBitmapFor(idx, f) + + 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 + } + decls groupBy bitmapCategory foreach { + case (nme.NO_NAME, _) => () + case (category, fields) => fold(fields, category) + } +// bitmaps.toList + Nil } - protected 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 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(field: Symbol): Symbol = { + val sym = clazz.info.decl(bitmapName(field)) + + assert(!sym.isOverloaded, sym) + + sym orElse newBitmapFor(field) } + private def newBitmapFor(field: Symbol): Symbol = { + val bitmapSym = + clazz.newVariable(bitmapName(field), clazz.pos) setInfo bitmapType(field) setFlag PrivateLocal setPos clazz.pos - type WithInitChecks = List[Tree] + bitmapSym addAnnotation VolatileAttr + if (isTransientField(field)) bitmapSym addAnnotation TransientAttr - /** 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: WithInitChecks): 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) + clazz.info.decls.enter(bitmapSym) + addDef(ValDef(bitmapSym, if (bitmapSym.info == BooleanClass) FALSE else ZERO)) + + bitmapSym } protected def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass - def addInitChecks(stats: List[Tree]): WithInitChecks = { - /* 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: Unit = { - def fold(fields: List[Symbol], category: Name) = { - var idx = 0 - fields foreach { f => - fieldOffset(f) = idx - idx += 1 - } + // overridden in CheckInitAccessorInitialization + def rhsWithOtherInitCheck(sym: Symbol)(rhs: Tree): Tree = rhs - 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) - } - } + /** Complete lazy field accessors. Applies only to classes, + * for its own (non inherited) lazy fields. + */ + def rhsWithInitCheck(sym: Symbol)(rhs: Tree): Tree = { + if (!clazz.isTrait && sym.isLazy && rhs != EmptyTree) { + rhs match { + case rhs if isUnitGetter(sym) => + mkLazyMemberDef(sym, List(rhs), UNIT) - /** 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 addInitCheck(stat: DefDef): DefDef = { - val sym = stat.symbol - - if (!clazz.isTrait && sym.isLazy && stat.rhs != EmptyTree) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnitGetter(sym) => - mkLazyMemberDef(sym, List(t), UNIT, fieldOffset(sym)) - - case Block(stats, res) => - mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) - - case t => t // pass specialized lazy vals through - } + case Block(stats, res) => + mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol)) + + case rhs => rhs } - else addCheckedInitCheck(stat) } + else rhsWithOtherInitCheck(sym)(rhs) + } + + override def deriveStatsWithInitChecks(stats: List[Tree]): List[Tree] = { + bitmapsFor(clazz.info.decls.toList) - buildBitmapOffsets +// foreach { bitmapSym => +// clazz.info.decls.enter(bitmapSym) +// addDef(ValDef(bitmapSym, if (bitmapSym.info == BooleanClass) FALSE else ZERO)) +// } stats mapConserve { - case dd: DefDef => addInitCheck(dd) + case dd: DefDef => deriveDefDef(dd)(rhsWithInitCheck(dd.symbol)) case stat => stat } } + + private def maskForOffset(sym: Symbol, kind: ClassSymbol): Constant = + if (kind == LongClass) Constant(1L << offsetInBitmap(sym)) + else Constant(1 << offsetInBitmap(sym)) + + /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the * precise comparison operator depending on the value of 'equalToZero'. */ - protected def mkTest(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) + protected def mkTest(field: Symbol, equalToZero: Boolean): Tree = { + val bitmapSym = bitmapFor(field) + val bitmapTree = This(clazz) DOT bitmapSym + val bitmapClass = bitmapSym.info.typeSymbol.asClass + + if (bitmapClass == BooleanClass) { + if (equalToZero) NOT(bitmapTree) else bitmapTree + } else { + val mask = Literal(maskForOffset(field, bitmapClass)) + val lhs = bitmapTree GEN_&(mask, bitmapClass) + if (equalToZero) lhs GEN_==(ZERO, bitmapClass) + else lhs GEN_!=(ZERO, bitmapClass) } } - /* - * 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(offset: RunId, field: global.Symbol): Symbol = { - val category = bitmapCategory(field) - val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName - val sym = clazz.info.decl(bitmapName) - - assert(!sym.isOverloaded, sym) - - def createBitmap: Symbol = { - val bitmapKind = bitmapKindForCategory(category) - val sym = clazz.newVariable(bitmapName, clazz.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 - clazz.info.decls.enter(sym) - addDef(clazz.pos, init) - sym - } - - sym orElse createBitmap - } /* Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ - def mkSetFlag(offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { - val bmp = bitmapFor(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)) + def mkSetFlag(valSym: Symbol): Tree = { + val bitmapSym = bitmapFor(valSym) + val bitmapClass = bitmapSym.info.typeSymbol.asClass + def x = This(clazz) DOT bitmapSym + + // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?) + Assign(x, + if (bitmapClass == BooleanClass) TRUE + else Apply(Select(x, getMember(bitmapClass, nme.OR)), List(Literal(maskForOffset(valSym, bitmapClass)))) + ) - x === newValue } @@ -291,53 +281,57 @@ trait InitBitmaps extends Transform with ast.TreeDSL { * @param init The tree which initializes the field ( f = ) * @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$ + * The result will be a tree of the form `if ((bitmap&n & MASK) == 0) this.l$compute() else l$` + * A synthetic compute method will also be added to the current class, of the following shape: + * ``` + * def l$compute() = { + * synchronized(this) { + * if ((bitmap$n & MASK) == 0) { + * init // l$ = + * bitmap$n = bimap$n | MASK + * } + * } + * ... + * this.f1 = null + * ... + * this.fn = null + * l$ + * } + * ``` * - * ... - * def l$compute() = { synchronized(this) { - * if ((bitmap$n & MASK) == 0) { - * init // l$ = - * bitmap$n = bimap$n | MASK - * }} - * 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)). * - * ... - * 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. + * 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 def mkLazyMemberDef(lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = { - def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) + private def mkLazyMemberDef(lzyVal: Symbol, init: List[Tree], retVal: Tree): Tree = { + def thisRef = gen.mkAttributedThis(clazz) + def cond = mkTest(lzyVal, equalToZero = true) - val bitmapSym = bitmapFor(offset, lzyVal) - val kind = bitmapKind(lzyVal) - val mask = maskForOffset(offset, lzyVal, kind) - val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify + val slowPathDef = { + def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) + val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify - if (nulls.nonEmpty) - log("nulling fields inside " + lzyVal + ": " + nulls) + if (nulls.nonEmpty) + log("nulling fields inside " + lzyVal + ": " + nulls) - val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? - val slowPathSym = - clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) + val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? + val slowPathSym = + clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) - def thisRef = gen.mkAttributedThis(clazz) - def cond = mkTest(mask, bitmapSym, equalToZero = true, kind) + val statsToSynch = init ::: List(mkSetFlag(lzyVal), UNIT) + val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) + + // TODO: this code used to run after classes were flattened -- do we have all the needed owner changes? + DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(lzyVal -> slowPathSym)), retVal)) + } - val statsToSynch = init ::: List(mkSetFlag(offset, lzyVal, kind), UNIT) - val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) - addDef(pos, DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(currentOwner -> slowPathSym)), retVal))) + addDef(slowPathDef) - typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathSym), Nil), retVal)) + typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathDef.symbol), Nil), retVal)) } /** Map lazy values to the fields they should null after initialization. */ @@ -364,7 +358,7 @@ trait InitBitmaps extends Transform with ast.TreeDSL { && sym.isPrivate && !(currentOwner.isGetter && currentOwner.accessed == sym) // getter && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) - && sym.owner == templ.symbol.owner + && sym.owner == clazz && !sym.isLazy && !tree.isDef) { debuglog("added use in: " + currentOwner + " -- " + tree) @@ -375,7 +369,7 @@ trait InitBitmaps extends Transform with ast.TreeDSL { } } } - SingleUseTraverser(templ) + templStats foreach SingleUseTraverser.apply debuglog("usedIn: " + usedIn) usedIn filter { case (_, member :: Nil) => member.isValue && member.isLazy @@ -393,7 +387,7 @@ trait InitBitmaps extends Transform with ast.TreeDSL { } - protected class AddsCheckInitDefs(clazz: Symbol, templ: Template) extends StandardAccessorInitialization(clazz, templ) { + protected class CheckInitAccessorInitialization(clazz: Symbol, templStats: List[Tree]) extends LazyAccessorInitialization(clazz, templStats) { /** 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 @@ -402,7 +396,7 @@ trait InitBitmaps extends Transform with ast.TreeDSL { * That's why they are excluded. * Note: The `checkinit` option does not check if transient fields are initialized. */ - override def needsInitFlag(sym: Symbol): Boolean = ( + private def needsInitFlag(sym: Symbol): Boolean = ( sym.isGetter && !sym.isInitializedToDefault && !isConstantType(sym.info.finalResultType) // SI-4742 @@ -413,12 +407,33 @@ trait InitBitmaps extends Transform with ast.TreeDSL { && !(sym.accessed hasAnnotation TransientAttr) ) + private def needsInitFlagAndHasBitmap(sym: Symbol) = hasBitmap(sym) && needsInitFlag(sym) + +// override protected def isFieldWithTransientBitmap(field: Symbol) = +// super.isFieldWithTransientBitmap(field) || needsInitFlag(field) && !field.isDeferred && isTransientField(field) + + /** 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(field: Symbol): Name = { + import nme._ + + super.bitmapCategory(field) match { + case NO_NAME if needsInitFlag(field) && !field.isDeferred => + if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + case category => category + } + } + object addInitBitsTransformer extends Transformer { - private def checkedGetter(lhs: Tree) = { + private def checkedGetter(lhs: Tree)(pos: Position) = { val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitFlag(sym) && (fieldOffset contains sym)) { + if (needsInitFlagAndHasBitmap(sym)) { debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(localTyper typed mkSetFlag(fieldOffset(sym), sym, bitmapKind(sym))) + List(typedPos(pos)(mkSetFlag(sym))) } else Nil } @@ -427,7 +442,7 @@ trait InitBitmaps extends Transform with ast.TreeDSL { // that self is anything in particular? super.transformStats( stats flatMap { - case stat@Assign(lhs@Select(This(_), _), rhs) => stat :: checkedGetter(lhs) + 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) @@ -437,42 +452,27 @@ trait InitBitmaps extends Transform with ast.TreeDSL { } } - /** Adds statements to set the 'init' bit for each field initialized - * in the body of a constructor. - * @pre settings.checkInit - */ - def addInitBits(rhs: Tree): Tree = addInitBitsTransformer transform rhs - - override def addCheckedInitCheck(stat: DefDef): DefDef = { - val sym = stat.symbol - if (needsInitFlag(sym) && stat.rhs != EmptyTree && !clazz.hasFlag(TRAIT)) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat)(rhs => - (mkCheckedAccessor(_: Tree, fieldOffset(sym), stat.pos, sym)) ( - if (isUnitGetter(sym)) UNIT else rhs - ) - ) - } - else if (sym.isConstructor) { - deriveDefDef(stat)(addInitBits) + /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */ + override def rhsWithOtherInitCheck(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 (clazz.hasFlag(TRAIT) || rhs == EmptyTree) rhs + else if (needsInitFlag(sym)) { // implies sym.isGetter + mkCheckedAccessor(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym) } - else if (!clazz.isTrait && sym.isSetter) { + else if (sym.isSetter) { val getter = sym.getterIn(clazz) - if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) - deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) - else stat + if (needsInitFlagAndHasBitmap(getter)) + Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) + else rhs } - else stat + else rhs } - def mkCheckedAccessor(retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { - val sym = fieldSym.getterIn(fieldSym.owner) - val bitmapSym = bitmapFor(offset, sym) - val kind = bitmapKind(sym) - val mask = maskForOffset(offset, sym, kind) + def mkCheckedAccessor(retVal: Tree, pos: Position, getter: Symbol): Tree = { val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" val result = - IF(mkTest(mask, bitmapSym, equalToZero = false, kind)). + IF(mkTest(getter, equalToZero = false)). THEN(retVal). ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) @@ -482,12 +482,12 @@ trait InitBitmaps extends Transform with ast.TreeDSL { // TODO: need to run this on all accessor bodies (after fields refactoring, this is only run on accessors mixed in during mixins, which is only PRESUPER | PARAMACCESSOR) override def getterBody(getter: Symbol) = { if (!needsInitFlag(getter)) super.getterBody(getter) - else mkCheckedAccessor(super.getterBody(getter), fieldOffset(getter), getter.pos, getter) + else mkCheckedAccessor(super.getterBody(getter), getter.pos, getter) } override def setterBody(setter: Symbol, getter: Symbol) = { if (!needsInitFlag(getter)) super.setterBody(setter, getter) - else Block(List(super.setterBody(setter, getter)), mkSetFlag(fieldOffset(getter), getter, bitmapKind(getter))) + else Block(List(super.setterBody(setter, getter)), mkSetFlag(getter)) } } } @@ -569,7 +569,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { ) - + /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * * @param base The class in which everything is mixed together @@ -830,7 +830,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { protected def newTransformer(unit: CompilationUnit): Transformer = new MixinTransformer(unit) - class MixinTransformer(unit : CompilationUnit) extends InitializationTransformer { + class MixinTransformer(unit : CompilationUnit) extends Transformer with InitializationTransformation { + /** 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 = @@ -875,10 +878,11 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { * * @param clazz The class to which definitions are added */ - private def addNewDefs(clazz: Symbol, stats: List[Tree], accessorInit: AccessorInitialization): List[Tree] = { + private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { + val accessorInit = accessorInitialization(clazz, stats) import accessorInit._ - val statsWithInitChecks = addInitChecks(stats) + val statsWithInitChecks = deriveStatsWithInitChecks(stats) // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { @@ -948,11 +952,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { // 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 - val accessorInit = accessorInitialization(currentOwner, templ) - // add all new definitions to current class or interface - val statsWithNewDefs = addNewDefs(currentOwner, body, accessorInit) + val statsWithNewDefs = addNewDefs(currentOwner, body) statsWithNewDefs foreach { case dd: DefDef if isTraitMethodRequiringStaticImpl(dd) => dd.symbol.updateAttachment(NeedStaticImpl) -- cgit v1.2.3 From b2e0911ded57f33e9600c6337a307c0fe1877eff Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 19 Aug 2016 11:07:00 -0700 Subject: Fields does bitmaps & synch for lazy vals & modules Essentially, we fuse mixin and lazyvals into the fields phase. With fields mixing in trait members into subclasses, we have all info needed to compute bitmaps, and thus we can synthesize the synchronisation logic as well. By doing this before erasure we get better signatures, and before specialized means specialized lazy vals work now. Mixins is now almost reduced to its essence: implementing super accessors and forwarders. It still synthesizes accessors for param accessors and early init trait vals. Concretely, trait lazy vals are mixed into subclasses with the needed synchronization logic in place, as do lazy vals in classes and methods. Similarly, modules are initialized using double checked locking. Since the code to initialize a module is short, we do not emit compute methods for modules (anymore). For simplicity, local lazy vals do not get a compute method either. The strange corner case of constant-typed final lazy vals is resolved in favor of laziness, by no longer assigning a constant type to a lazy val (see widenIfNecessary in namers). If you explicitly ask for something lazy, you get laziness; with the constant-typedness implicit, it yields to the conflicting `lazy` modifier because it is explicit. Co-Authored-By: Lukas Rytz Fixes scala/scala-dev#133 Inspired by dotc, desugar a local `lazy val x = rhs` into ``` val x$lzy = new scala.runtime.LazyInt() def x(): Int = { x$lzy.synchronized { if (!x$lzy.initialized) { x$lzy.initialized = true x$lzy.value = rhs } x$lzy.value } } ``` Note that the 2.11 decoding (into a local variable and a bitmap) also creates boxes for local lazy vals, in fact two for each lazy val: ``` def f = { lazy val x = 0 x } ``` desugars to ``` public int f() { IntRef x$lzy = IntRef.zero(); VolatileByteRef bitmap$0 = VolatileByteRef.create((byte)0); return this.x$1(x$lzy, bitmap$0); } private final int x$lzycompute$1(IntRef x$lzy$1, VolatileByteRef bitmap$0$1) { C c = this; synchronized (c) { if ((byte)(bitmap$0$1.elem & 1) == 0) { x$lzy$1.elem = 0; bitmap$0$1.elem = (byte)(bitmap$0$1.elem | 1); } return x$lzy$1.elem; } } private final int x$1(IntRef x$lzy$1, VolatileByteRef bitmap$0$1) { return (byte)(bitmap$0$1.elem & 1) == 0 ? this.x$lzycompute$1(x$lzy$1, bitmap$0$1) : x$lzy$1.elem; } ``` An additional problem with the above encoding is that the `lzycompute` method synchronizes on `this`. In connection with the new lambda encoding that no longer generates anonymous classes, captured lazy vals no longer synchronize on the lambda object. The new encoding solves this problem (scala/scala-dev#133) by synchronizing on the lazy holder. Currently, we don't exploit the fact that the initialized field is `@volatile`, because it's not clear the performance is needed for local lazy vals (as they are not contended, and as soon as the VM warms up, biased locking should deal with that) Note, be very very careful when moving to double-checked locking, as this needs a different variation than the one we use for class-member lazy vals. A read of a volatile field of a class does not necessarily impart any knowledge about a "subsequent" read of another non-volatile field of the same object. A pair of volatile reads and write can be used to implement a lock, but it's not clear if the complexity is worth an unproven performance gain. (Once the performance gain is proven, let's change the encoding.) - don't explicitly init bitmap in bytecode - must apply method to () explicitly after uncurry --- project/ScalaOptionParser.scala | 2 +- src/compiler/scala/tools/ant/Scalac.scala | 2 +- src/compiler/scala/tools/nsc/Global.scala | 12 +- .../scala/tools/nsc/transform/Constructors.scala | 19 +- .../scala/tools/nsc/transform/Fields.scala | 246 +++++---- .../scala/tools/nsc/transform/LazyVals.scala | 310 ------------ src/compiler/scala/tools/nsc/transform/Mixin.scala | 561 ++++++++++----------- .../tools/nsc/transform/SpecializeTypes.scala | 11 +- .../scala/tools/nsc/typechecker/Namers.scala | 2 +- src/library/scala/runtime/LazyRef.scala | 52 ++ src/manual/scala/man1/scalac.scala | 4 +- .../scala/reflect/internal/Definitions.scala | 5 +- src/reflect/scala/reflect/internal/StdNames.scala | 1 + src/reflect/scala/reflect/internal/Symbols.scala | 2 +- .../scala/reflect/runtime/JavaUniverseForce.scala | 3 + src/repl/scala/tools/nsc/interpreter/Phased.scala | 4 +- test/files/neg/t6446-additional.check | 21 +- test/files/neg/t6446-missing.check | 19 +- test/files/neg/t6446-show-phases.check | 19 +- test/files/neg/t7494-no-options.check | 21 +- test/files/run/analyzerPlugins.check | 4 +- test/files/run/delambdafy_t6028.check | 6 +- test/files/run/lazy-locals-2.scala | 322 ++++++++++++ test/files/run/lazy_local_labels.check | 9 + test/files/run/lazy_local_labels.scala | 28 + test/files/run/programmatic-main.check | 19 +- test/files/run/t3569.check | 2 + test/files/run/t3569.scala | 8 +- test/files/run/t5552.check | 4 + test/files/run/t5552.scala | 10 +- test/files/run/t6028.check | 6 +- .../nsc/backend/jvm/opt/ScalaInlineInfoTest.scala | 3 - 32 files changed, 940 insertions(+), 797 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/transform/LazyVals.scala create mode 100644 src/library/scala/runtime/LazyRef.scala create mode 100644 test/files/run/lazy-locals-2.scala create mode 100644 test/files/run/lazy_local_labels.check create mode 100644 test/files/run/lazy_local_labels.scala diff --git a/project/ScalaOptionParser.scala b/project/ScalaOptionParser.scala index 77c9d765e9..af82f8fce5 100644 --- a/project/ScalaOptionParser.scala +++ b/project/ScalaOptionParser.scala @@ -94,7 +94,7 @@ object ScalaOptionParser { private def stringSettingNames = List("-Xgenerate-phase-graph", "-Xmain-class", "-Xpluginsdir", "-Xshow-class", "-Xshow-object", "-Xsource-reader", "-Ydump-classes", "-Ygen-asmp", "-Ypresentation-log", "-Ypresentation-replay", "-Yrepl-outdir", "-d", "-dependencyfile", "-encoding", "-Xscript") private def pathSettingNames = List("-bootclasspath", "-classpath", "-extdirs", "-javabootclasspath", "-javaextdirs", "-sourcepath", "-toolcp") - private val phases = List("all", "parser", "namer", "packageobjects", "typer", "patmat", "superaccessors", "extmethods", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "posterasure", "lazyvals", "lambdalift", "constructors", "flatten", "mixin", "cleanup", "delambdafy", "icode", "jvm", "terminal") + private val phases = List("all", "parser", "namer", "packageobjects", "typer", "patmat", "superaccessors", "extmethods", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "posterasure", "fields", "lambdalift", "constructors", "flatten", "mixin", "cleanup", "delambdafy", "icode", "jvm", "terminal") private val phaseSettings = List("-Xprint-icode", "-Ystop-after", "-Yskip", "-Yshow", "-Ystop-before", "-Ybrowse", "-Ylog", "-Ycheck", "-Xprint") private def multiStringSettingNames = List("-Xmacro-settings", "-Xplugin", "-Xplugin-disable", "-Xplugin-require") private def intSettingNames = List("-Xmax-classfile-name", "-Xelide-below", "-Ypatmat-exhaust-depth", "-Ypresentation-delay", "-Yrecursion") 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/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/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 6339f0002d..0b8705948c 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -56,8 +56,7 @@ import symtab.Flags._ * * TODO: check init support (or drop the -Xcheck-init flag??) */ -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._ @@ -139,13 +138,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) @@ -187,31 +185,29 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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)) + 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) + val cond = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL)) + val init = Assign(moduleVarRef, gen.newModule(module, moduleVar.info)) + + Block(List(gen.mkSynchronizedCheck(monitorHolder, cond, List(init), Nil)), moduleVarRef) } + // NoSymbol for lazy accessor sym with unit result type + def lazyVarOf(sym: Symbol) = moduleVarOf.getOrElse(sym, NoSymbol) - private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0, localLazyVal: Boolean = false): TermSymbol = { + private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0): TermSymbol = { val flags = member.flags | extraFlags - val name = member.name.toTermName - val pos = member.pos - - // 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. - val nameSuffix = - if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING - else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING - - // TODO: should end up final in bytecode - val fieldFlags = - if (!localLazyVal) flags & FieldFlags | PrivateLocal | MUTABLE - else (flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) - -// println(s"new lazy var sym in $owner for $member ${symtab.Flags.flagsToString(fieldFlags)}") - val sym = owner.newValue(name.append(nameSuffix), pos.focus, fieldFlags | extraFlags) setInfo tp + val pos = member.pos + val name = member.name.toTermName.append(reflect.NameTransformer.LOCAL_SUFFIX_STRING) + + // the underlying field for a lazy val should not be final because we write to it outside of a constructor, + // so, set the MUTABLE flag + val fieldFlags = flags & FieldFlags | PrivateLocal | MUTABLE + + val sym = owner.newValue(name, pos.focus, fieldFlags | extraFlags) setInfo tp moduleVarOf(member) = sym sym } @@ -292,7 +288,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // 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 // 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. @@ -304,7 +300,7 @@ 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) && !(member hasFlag LAZY)) @@ -347,19 +343,18 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // 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 modulesAndLazyValsNeedingExpansion = - oldDecls.toList.filter(m => - (m.isModule && (!m.isStatic || m.isOverridingSymbol)) - || (m.isLazy && !(m.info.isInstanceOf[ConstantType] || isUnitType(m.info))) // no need for ASF since we're in the defining class - ) + 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 expandedModulesAndLazyVals = - modulesAndLazyValsNeedingExpansion map { member => + val expandedModulesAndLazyVals = ( + modulesAndLazyValsNeedingExpansion flatMap { member => if (member.isLazy) { - newLazyVarMember(member) + List(newLazyVarMember(member), accessorSymbolSynth.newSlowPathSymbol(member)) } // expanding module def (top-level or nested in static module) - else if (member.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, member) @@ -368,8 +363,8 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // must reuse symbol instead of creating an accessor member setFlag NEEDS_TREES newModuleVarMember(member) - } - } + }) + }) // println(s"expanded modules for $clazz: $expandedModules") @@ -400,7 +395,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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}") @@ -416,9 +411,10 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else if (member hasFlag LAZY) { val mixedinLazy = cloneAccessor() val lazyVar = newLazyVarMember(mixedinLazy) - List(lazyVar, newSuperLazy(mixedinLazy, site, lazyVar)) + // 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).stored) { + 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) @@ -432,13 +428,15 @@ 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 (expandedModulesAndLazyVals.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 @@ -449,10 +447,15 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor oldDecls foreach { d => if (!omittableField(d)) enter(d) } 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) @@ -468,50 +471,109 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (!module.isStatic) module setFlag METHOD | STABLE } - class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - def mkTypedUnit(pos: Position) = localTyper.typedPos(pos)(CODE.UNIT) - def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos)) + class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with CheckedAccessorTreeSynthesis { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) - def mkAccessor(accessor: Symbol)(body: Tree) = localTyper.typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + 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 mkField(sym: Symbol) = localTyper.typedPos(sym.pos)(ValDef(sym)).asInstanceOf[ValDef] + 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 mkField(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(): Int = + * x$lzy.synchronized { + * if (!x$lzy.initialized) { + * x$lzy.initialized = true + * x$lzy.value = rhs + * } + * x$lzy.value + * } + * ``` + */ + private def mkLazyLocalDef(lazyVal: Symbol, rhs: Tree): Tree = { + 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 flags = (lazyVal.flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) // TODO: why include MUTABLE??? + val name = lazyVal.name.toTermName.append(nme.LAZY_LOCAL_SUFFIX_STRING) + val holderSym = + lazyVal.owner.newValue(name, lazyVal.pos, flags) setInfo refTpe + + val accessor = mkAccessor(lazyVal) { + import CODE._ + val initializedGetter = refTpe.member(nme.initialized) + val setInitialized = Apply(Select(Ident(holderSym), initializedGetter.setterIn(refClass)), TRUE :: Nil) + val isUnit = refClass == LazyUnitClass + val valueGetter = if (isUnit) NoSymbol else refTpe.member(nme.value) + val valueSetter = if (isUnit) NoSymbol else valueGetter.setterIn(refClass) + val setValue = if (isUnit) rhs else Apply(Select(Ident(holderSym), valueSetter), rhs :: Nil) + val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) + gen.mkSynchronized(Ident(holderSym), + Block(List( + If(NOT(Ident(holderSym) DOT initializedGetter), + Block(List( + setInitialized), + setValue), + EmptyTree)), + getValue)) // must read the value within the synchronized block since it's not volatile + // (there's no happens-before relation with the read of the volatile initialized field) + // TODO: double-checked locking + } + // do last! + // remove LAZY: prevent lazy expansion in mixin + // 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(LAZY | STABLE | ACCESSOR) + Thicket(mkField(holderSym, New(refTpe)) :: 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 @@ -519,15 +581,17 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor else moduleInit(module) ) - def superLazy(getter: Symbol): Some[Tree] = { + 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? - Some(gen.mkAssignAndReturn(moduleVarOf(getter), Apply(Select(Super(This(clazz), tpnme.EMPTY), getter.name), Nil))) + 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) map mkAccessor(getter) + 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) @@ -538,7 +602,6 @@ 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 statSym = stat.symbol @@ -564,37 +627,28 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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 => + && fieldMemoizationIn(statSym, clazz).constantTyped => deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) - /** Normalize ValDefs to corresponding accessors + field - * - * ValDef in trait --> getter DefDef - * Lazy val receives field with a new symbol (if stored) and the ValDef's symbol is moved to a DefDef (the lazy accessor): - * - 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 = ; z } where z can be an identifier or a field. - */ + // 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, clazz) => - def notStored = {val resultType = statSym.info.resultType ; (resultType.isInstanceOf[ConstantType] || isUnitType(resultType))} val transformedRhs = atOwner(statSym)(transform(rhs)) if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) - else if (clazz.isTrait || notStored) mkAccessor(statSym)(transformedRhs) - else if (clazz.isClass) mkAccessor(statSym)(gen.mkAssignAndReturn(moduleVarOf(vd.symbol), transformedRhs)) + else if (clazz.isTrait) mkAccessor(statSym)(transformedRhs) + else if (!clazz.isClass) mkLazyLocalDef(vd.symbol, transformedRhs) else { - // local lazy val (same story as modules: info transformer doesn't get here, so can't drive tree synthesis) - val lazyVar = newLazyVarSymbol(currentOwner, statSym, statSym.info.resultType, extraFlags = 0, localLazyVal = true) - val lazyValInit = gen.mkAssignAndReturn(lazyVar, transformedRhs) - Thicket(mkField(lazyVar) :: mkAccessor(statSym)(lazyValInit) :: Nil) + // 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(clazz) + synthAccessorInClass.expandLazyClassMember(lazyVarOf(statSym), statSym, transformedRhs, nullables.getOrElse(clazz, 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, clazz).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. @@ -616,22 +670,24 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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 ba2f6082e0..0000000000 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ /dev/null @@ -1,310 +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) = mkLazyLocalDef(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) = mkDoubleCheckedLocking(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 mkDoubleCheckedLocking(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], stats: List[Tree], retVal: Tree): (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) - - val slowPathDef: Tree = { - 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(gen.mkAttributedThis(clazz), cond, syncBody, stats).changeOwner(currentOwner -> defSym) - - DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) - } - - - (If(cond, Apply(Ident(defSym), Nil), retVal), slowPathDef) - } - - /** return a 'lazified' version of rhs. Rhs should conform to the - * following schema: - * { - * l$ = - * l$ - * } or - * 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$ = - * 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) { - * ; - * bitmap$n = bimap$n | MASK - * }} - * () - * } - * } - */ - val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil - private def mkLazyLocalDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = { - /** Return the symbol corresponding of the right bitmap int inside meth, - * given offset. - */ - val bitmapSym = { - val n = offset / FLAGS_PER_BYTE - val bmps = bitmaps(methOrClass) - if (bmps.length > n) - bmps(n) - else { - val sym = methOrClass.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), methOrClass.pos).setInfo(ByteTpe) - enteringTyper { - sym addAnnotation VolatileAttr - } - - bitmaps(methOrClass) = (sym :: bmps).reverse - sym - } - } - - val mask = LIT(1 << (offset % FLAGS_PER_BYTE)) - val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym) - - debuglog(s"create complete lazy def in $methOrClass for $lazyVal") - - val (stmt, res) = tree match { - case Block(List(assignment), res) if !lazyUnit(lazyVal) => (assignment, res) - case rhs => (rhs, UNIT) - } - val block = Block(List(stmt, bitmapRef === (bitmapRef GEN_| (mask, bitmapKind))), UNIT) - - def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) - val lazyDefs = mkDoubleCheckedLocking(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) - (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 })) - } - } -} diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index b3558d6281..190755ff53 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -11,33 +11,34 @@ import Flags._ import scala.annotation.tailrec import scala.collection.mutable -// TODO: move lazy vals bitmap creation to lazy vals phase now that lazy vals are mixed in during fields -trait InitBitmaps extends Transform with ast.TreeDSL { +trait AccessorSynthesis extends Transform with ast.TreeDSL { import global._ import definitions._ import CODE._ - /** 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. - * - * DO NOT USE THIS MAP DIRECTLY, go through convenience methods - */ - private val _fieldOffset = perRunCaches.newMap[Symbol, Int]() + 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 + } - private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() - trait InitializationTransformation { + trait AccessorTreeSynthesis { protected def typedPos(pos: Position)(tree: Tree): Tree - def accessorInitialization(clazz: Symbol, templStats: List[Tree]): AccessorInitialization = - if (settings.checkInit) new CheckInitAccessorInitialization(clazz, templStats) - else new LazyAccessorInitialization(clazz, templStats) - - class AccessorInitialization(protected val clazz: Symbol){ - private val _newDefs = mutable.ListBuffer[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 deriveStatsWithInitChecks(stats: List[Tree]): List[Tree] = stats + def newDefs = _newDefs.toList /** Add tree at given position as new definition */ protected def addDef(tree: ValOrDefDef): Unit = _newDefs += typedPos(position(tree.symbol))(tree) @@ -93,196 +94,208 @@ trait InitBitmaps extends Transform with ast.TreeDSL { Select(This(clazz), accessor.accessed) } + } + case class BitmapInfo(symbol: Symbol, mask: Literal) { + def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass + } - protected class LazyAccessorInitialization(clazz: Symbol, templStats: List[Tree]) extends AccessorInitialization(clazz) { - private def bitmapNumber(sym: Symbol): Int = _fieldOffset(sym) / flagsPerBitmap(sym) - private def offsetInBitmap(sym: Symbol) = _fieldOffset(sym) % flagsPerBitmap(sym) - protected def hasBitmap(sym: Symbol) = _fieldOffset isDefinedAt sym - private def flagsPerBitmap(field: Symbol): Int = - bitmapKind(field) match { - case BooleanClass => 1 - case ByteClass => 8 - case IntClass => 32 - case LongClass => 64 - } + // 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]() - protected def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) - protected def bitmapType(field: Symbol): Type = bitmapKind(field).tpe - protected def bitmapName(field: Symbol): TermName = nme.newBitmapName(bitmapCategory(field), bitmapNumber(field)).toTermName + def checkedAccessorSymbolSynth(clz: Symbol) = + if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz } + else new CheckedAccessorSymbolSynth { val clazz = clz } - protected def isTransientField(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr -// protected def isFieldWithTransientBitmap(field: Symbol) = isTransientField(field) && isFieldWithBitmap(field) + // 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 - /** 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(field: Symbol): Name = { - 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 - } + def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized - import nme._ - if (isFieldWithBitmap(field)) - if (isTransientField(field)) BITMAP_TRANSIENT else BITMAP_NORMAL - else NO_NAME - } + /** 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 - /** Fill the map from fields to offset numbers. And from bitmap category to the type used for the bitmap field (its "kind"). - * - * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later. - */ - def bitmapsFor(decls: List[Symbol]): List[Symbol] = { -// val bitmaps = mutable.ListBuffer.empty[Symbol] - def fold(fields: List[Symbol], category: Name) = { - var idx = 0 - fields foreach { f => - _fieldOffset(f) = idx -// bitmaps += newBitmapFor(idx, f) - - idx += 1 - } + import nme._ - 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 - } - decls groupBy bitmapCategory foreach { - case (nme.NO_NAME, _) => () - case (category, fields) => fold(fields, category) - } -// bitmaps.toList - Nil - } + if (needsBitmap(sym) && sym.isLazy) + if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL + else NO_NAME + } - /* - * 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(field: Symbol): Symbol = { - val sym = clazz.info.decl(bitmapName(field)) + def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym) + protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym - assert(!sym.isOverloaded, sym) - sym orElse newBitmapFor(field) - } + /** 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 + ) - private def newBitmapFor(field: Symbol): Symbol = { - val bitmapSym = - clazz.newVariable(bitmapName(field), clazz.pos) setInfo bitmapType(field) setFlag PrivateLocal setPos clazz.pos + bitmapSym addAnnotation VolatileAttr - bitmapSym addAnnotation VolatileAttr - if (isTransientField(field)) bitmapSym addAnnotation TransientAttr + if (isTransientCategory) bitmapSym addAnnotation TransientAttr - clazz.info.decls.enter(bitmapSym) - addDef(ValDef(bitmapSym, if (bitmapSym.info == BooleanClass) FALSE else ZERO)) + bitmapSym + } - 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) - protected def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + _bitmapInfo(f) = BitmapInfo(bitmapSyms(bitmapIdx), Literal(mask)) + } - // overridden in CheckInitAccessorInitialization - def rhsWithOtherInitCheck(sym: Symbol)(rhs: Tree): Tree = rhs + bitmapSyms + } - /** Complete lazy field accessors. Applies only to classes, - * for its own (non inherited) lazy fields. - */ - def rhsWithInitCheck(sym: Symbol)(rhs: Tree): Tree = { - if (!clazz.isTrait && sym.isLazy && rhs != EmptyTree) { - rhs match { - case rhs if isUnitGetter(sym) => - mkLazyMemberDef(sym, List(rhs), UNIT) + decls groupBy bitmapCategory flatMap { + case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category) + case _ => Nil + } toList + } - case Block(stats, res) => - mkLazyMemberDef(sym, stats, Select(This(clazz), res.symbol)) + def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal) - case rhs => rhs - } - } - else rhsWithOtherInitCheck(sym)(rhs) - } + 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 + } - override def deriveStatsWithInitChecks(stats: List[Tree]): List[Tree] = { - bitmapsFor(clazz.info.decls.toList) + } -// foreach { bitmapSym => -// clazz.info.decls.enter(bitmapSym) -// addDef(ValDef(bitmapSym, if (bitmapSym.info == BooleanClass) FALSE else ZERO)) -// } + 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._ - stats mapConserve { - case dd: DefDef => deriveDefDef(dd)(rhsWithInitCheck(dd.symbol)) - case stat => stat - } + 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) + } - private def maskForOffset(sym: Symbol, kind: ClassSymbol): Constant = - if (kind == LongClass) Constant(1L << offsetInBitmap(sym)) - else Constant(1 << offsetInBitmap(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'. */ - protected def mkTest(field: Symbol, equalToZero: Boolean): Tree = { - val bitmapSym = bitmapFor(field) - val bitmapTree = This(clazz) DOT bitmapSym - val bitmapClass = bitmapSym.info.typeSymbol.asClass + def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = { + val bitmap = bitmapFor(field) + val bitmapTree = thisRef DOT bitmap.symbol - if (bitmapClass == BooleanClass) { + if (bitmap.storageClass == BooleanClass) { if (equalToZero) NOT(bitmapTree) else bitmapTree } else { - val mask = Literal(maskForOffset(field, bitmapClass)) - val lhs = bitmapTree GEN_&(mask, bitmapClass) - if (equalToZero) lhs GEN_==(ZERO, bitmapClass) - else lhs GEN_!=(ZERO, bitmapClass) + 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'. */ + /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ def mkSetFlag(valSym: Symbol): Tree = { - val bitmapSym = bitmapFor(valSym) - val bitmapClass = bitmapSym.info.typeSymbol.asClass - def x = This(clazz) DOT bitmapSym + val bitmap = bitmapFor(valSym) + def x = thisRef DOT bitmap.symbol - // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?) Assign(x, - if (bitmapClass == BooleanClass) TRUE - else Apply(Select(x, getMember(bitmapClass, nme.OR)), List(Literal(maskForOffset(valSym, bitmapClass)))) + 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 + } ) - } + } - - /** 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 lzyVal The symbol of this lazy field - * @param init The tree which initializes the field ( f = ) - * @param offset The offset of this field in the flags bitmap + class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass { + /** + * The compute method (slow path) looks like: * - * The result will be a tree of the form `if ((bitmap&n & MASK) == 0) this.l$compute() else l$` - * A synthetic compute method will also be added to the current class, of the following shape: * ``` * def l$compute() = { * synchronized(this) { @@ -306,134 +319,106 @@ trait InitBitmaps extends Transform with ast.TreeDSL { * * 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. */ - private def mkLazyMemberDef(lzyVal: Symbol, init: List[Tree], retVal: Tree): Tree = { - def thisRef = gen.mkAttributedThis(clazz) - def cond = mkTest(lzyVal, equalToZero = true) + 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 slowPathDef = { - def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) - val nulls = lazyValNullables.getOrElse(lzyVal, Nil) map nullify + val nulls = nullables.getOrElse(lazyAccessor, Nil) map nullify - if (nulls.nonEmpty) - log("nulling fields inside " + lzyVal + ": " + nulls) + if (nulls.nonEmpty) + log("nulling fields inside " + lazyAccessor + ": " + nulls) - val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else clazz.pos // TODO: is the else branch ever taken? - val slowPathSym = - clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfoAndEnter MethodType(Nil, lzyVal.tpe.resultType) + val slowPathSym = slowPathFor(lazyAccessor) + val rhsAtSlowDef = transformedRhs.changeOwner(lazyAccessor -> slowPathSym) - val statsToSynch = init ::: List(mkSetFlag(lzyVal), UNIT) - val synchedRhs = gen.mkSynchronizedCheck(thisRef, cond, statsToSynch, nulls) + val isUnit = isUnitGetter(lazyAccessor) + val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) + val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) - // TODO: this code used to run after classes were flattened -- do we have all the needed owner changes? - DefDef(slowPathSym, Block(List(synchedRhs.changeOwner(lzyVal -> slowPathSym)), retVal)) - } + val synchedStats = storeRes :: mkSetFlag(lazyAccessor) :: Nil + val slowPathRhs = + Block(List(gen.mkSynchronizedCheck(thisRef, mkTest(lazyAccessor), synchedStats, nulls)), selectVar) - addDef(slowPathDef) + // 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(mkTest(lazyAccessor), Apply(Select(thisRef, slowPathSym), Nil), selectVar) - typedPos(init.head.pos)(If(cond, Apply(Select(thisRef, slowPathDef.symbol), Nil), retVal)) + 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. */ - private lazy val lazyValNullables: 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 { - 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 == clazz - && !sym.isLazy - && !tree.isDef) { - debuglog("added use in: " + currentOwner + " -- " + tree) - usedIn(sym) ::= currentOwner - } - } - super.traverse(tree) - } + /** 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 - debuglog("usedIn: " + usedIn) - usedIn filter { - case (_, member :: Nil) => member.isValue && member.isLazy - case _ => false - } toMap } + templStats foreach SingleUseTraverser.apply + // println("usedIn: " + usedIn) - val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() - // check what fields can be nulled for - for ((field, users) <- singleUseFields; lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr)) - map(lazyFld) += field + // 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 - map.mapValues(_.toList sortBy (_.id)).toMap + // println("singlyUsedIn: " + singlyUsedIn) + singlyUsedIn } - } - - - protected class CheckInitAccessorInitialization(clazz: Symbol, templStats: List[Tree]) extends LazyAccessorInitialization(clazz, templStats) { - /** 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): 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) - ) - - private def needsInitFlagAndHasBitmap(sym: Symbol) = hasBitmap(sym) && needsInitFlag(sym) - -// override protected def isFieldWithTransientBitmap(field: Symbol) = -// super.isFieldWithTransientBitmap(field) || needsInitFlag(field) && !field.isDeferred && isTransientField(field) - /** 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(field: Symbol): Name = { - import nme._ + 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 - super.bitmapCategory(field) match { - case NO_NAME if needsInitFlag(field) && !field.isDeferred => - if (isTransientField(field)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT - case category => category - } + map.mapValues(_.toList sortBy (_.id)).toMap } + } + - object addInitBitsTransformer extends Transformer { + class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth { + private object addInitBitsTransformer extends Transformer { private def checkedGetter(lhs: Tree)(pos: Position) = { - val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitFlagAndHasBitmap(sym)) { - debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(typedPos(pos)(mkSetFlag(sym))) + 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 } @@ -453,23 +438,21 @@ trait InitBitmaps extends Transform with ast.TreeDSL { } /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */ - override def rhsWithOtherInitCheck(sym: Symbol)(rhs: Tree): Tree = { + 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 (clazz.hasFlag(TRAIT) || rhs == EmptyTree) rhs - else if (needsInitFlag(sym)) { // implies sym.isGetter - mkCheckedAccessor(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym) - } + 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 (needsInitFlagAndHasBitmap(getter)) - Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) + if (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) else rhs } else rhs } - def mkCheckedAccessor(retVal: Tree, pos: Position, getter: Symbol): Tree = { + 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)). @@ -478,23 +461,11 @@ trait InitBitmaps extends Transform with ast.TreeDSL { typedPos(pos)(BLOCK(result, retVal)) } - - // TODO: need to run this on all accessor bodies (after fields refactoring, this is only run on accessors mixed in during mixins, which is only PRESUPER | PARAMACCESSOR) - override def getterBody(getter: Symbol) = { - if (!needsInitFlag(getter)) super.getterBody(getter) - else mkCheckedAccessor(super.getterBody(getter), getter.pos, getter) - } - - override def setterBody(setter: Symbol, getter: Symbol) = { - if (!needsInitFlag(getter)) super.setterBody(setter, getter) - else Block(List(super.setterBody(setter, getter)), mkSetFlag(getter)) - } } } } - -abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { +abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis { import global._ import definitions._ import CODE._ @@ -830,7 +801,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { protected def newTransformer(unit: CompilationUnit): Transformer = new MixinTransformer(unit) - class MixinTransformer(unit : CompilationUnit) extends Transformer with InitializationTransformation { + 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) @@ -879,10 +850,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { * @param clazz The class to which definitions are added */ private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { - val accessorInit = accessorInitialization(clazz, stats) - import accessorInit._ - - val statsWithInitChecks = deriveStatsWithInitChecks(stats) + 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) { @@ -904,10 +873,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { } } - val addedStatsWithInitBitsAndChecks = implementWithNewDefs(statsWithInitChecks) + val implementedAccessors = implementWithNewDefs(stats) if (clazz.isTrait) - addedStatsWithInitBitsAndChecks filter { + implementedAccessors filter { case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false case _ => true } @@ -929,7 +898,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with InitBitmaps { stat } - addedStatsWithInitBitsAndChecks map completeSuperAccessor + implementedAccessors map completeSuperAccessor } } 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/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index fe7a4d5fef..3d2b689e4c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -982,7 +982,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/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 10ae68cdd1..ac025e50ae 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -1682,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 = { 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/test/files/neg/t6446-additional.check b/test/files/neg/t6446-additional.check index 45db63317c..23df978cd9 100644 --- a/test/files/neg/t6446-additional.check +++ b/test/files/neg/t6446-additional.check @@ -10,19 +10,18 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - ploogin 25 A sample phase that does so many things it's kind of hard... - terminal 26 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + ploogin 24 A sample phase that does so many things it's kind of hard... + terminal 25 the last phase during a compilation run diff --git a/test/files/neg/t6446-missing.check b/test/files/neg/t6446-missing.check index 04523d18e6..c0a8fea140 100644 --- a/test/files/neg/t6446-missing.check +++ b/test/files/neg/t6446-missing.check @@ -11,18 +11,17 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - terminal 25 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + terminal 24 the last phase during a compilation run diff --git a/test/files/neg/t6446-show-phases.check b/test/files/neg/t6446-show-phases.check index 03f8273c17..cf8595db5d 100644 --- a/test/files/neg/t6446-show-phases.check +++ b/test/files/neg/t6446-show-phases.check @@ -10,18 +10,17 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - terminal 25 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + terminal 24 the last phase during a compilation run diff --git a/test/files/neg/t7494-no-options.check b/test/files/neg/t7494-no-options.check index bb143e8644..138d2fe9a3 100644 --- a/test/files/neg/t7494-no-options.check +++ b/test/files/neg/t7494-no-options.check @@ -11,19 +11,18 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - ploogin 25 A sample phase that does so many things it's kind of hard... - terminal 26 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + ploogin 24 A sample phase that does so many things it's kind of hard... + terminal 25 the last phase during a compilation run diff --git a/test/files/run/analyzerPlugins.check b/test/files/run/analyzerPlugins.check index 9643079b83..64b68db242 100644 --- a/test/files/run/analyzerPlugins.check +++ b/test/files/run/analyzerPlugins.check @@ -39,7 +39,7 @@ pluginsPt(?, Trees$TypeBoundsTree) [2] pluginsPt(?, Trees$TypeDef) [1] pluginsPt(?, Trees$TypeTree) [32] pluginsPt(?, Trees$Typed) [1] -pluginsPt(?, Trees$ValDef) [21] +pluginsPt(?, Trees$ValDef) [13] pluginsPt(Any, Trees$Literal) [2] pluginsPt(Any, Trees$Typed) [1] pluginsPt(Array[Any], Trees$ArrayValue) [1] @@ -109,7 +109,7 @@ pluginsTyped(, Trees$ClassDef) [2] pluginsTyped(, Trees$DefDef) [14] pluginsTyped(, Trees$PackageDef) [1] pluginsTyped(, Trees$TypeDef) [1] -pluginsTyped(, Trees$ValDef) [21] +pluginsTyped(, Trees$ValDef) [13] pluginsTyped(=> Boolean @testAnn, Trees$Select) [1] pluginsTyped(=> Double, Trees$Select) [1] pluginsTyped(=> Int, Trees$Select) [5] diff --git a/test/files/run/delambdafy_t6028.check b/test/files/run/delambdafy_t6028.check index 8b0ae7e9b9..b188928c09 100644 --- a/test/files/run/delambdafy_t6028.check +++ b/test/files/run/delambdafy_t6028.check @@ -42,7 +42,7 @@ package { def $outer(): T = MethodLocalObject$2.this.$outer; def $outer(): T = MethodLocalObject$2.this.$outer }; - final private[this] def MethodLocalObject$lzycompute$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { + final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { T.this.synchronized({ if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); @@ -50,10 +50,6 @@ package { }); MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; - final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1) - else - MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](); final private[this] def $anonfun$tryy$1(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try { tryyLocal$1.elem = tryyParam$1 } finally () diff --git a/test/files/run/lazy-locals-2.scala b/test/files/run/lazy-locals-2.scala new file mode 100644 index 0000000000..cb905d3bef --- /dev/null +++ b/test/files/run/lazy-locals-2.scala @@ -0,0 +1,322 @@ +object Logs { + val logBuf = new collection.mutable.StringBuilder() + def log(m: Any): Unit = { if (logBuf.nonEmpty) logBuf.append(":"); logBuf.append(m) } + def checkLog(expected: String): Unit = { + val res = logBuf.toString + assert(res == expected, s"expected:\n$expected\nfound:\n$res") + logBuf.clear() + } +} + +import Logs._ + +class C { + def getInt : Int = { log("getInt"); 1 } + def getString: String = { log("getString"); "s" } + def getUnit : Unit = { log("getUnit") } + + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + checkLog("") + + def m1 = { + lazy val t1 = getInt + t1 + t1 + } + def m2 = { + lazy val t1 = getString + t1 + t1 + } + def m3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + val vl1 = { + lazy val t1 = getInt + t1 + t1 + } + val vl2 = { + lazy val t1 = getString + t1 + t1 + } + val vl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + var vr1 = { + lazy val t1 = getInt + t1 + t1 + } + var vr2 = { + lazy val t1 = getString + t1 + t1 + } + var vr3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + lazy val lvl1 = { + lazy val t1 = getInt + t1 + t1 + } + lazy val lvl2 = { + lazy val t1 = getString + t1 + t1 + } + lazy val lvl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + { + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + + log(t1 + t1) + log(t2 + t2) + log(t3); log(t3) + } + checkLog("getInt:2:getString:ss:getUnit:():()") + + + def run(): Unit = { + log(t1); log(t1); + log(t2); log(t2); + log(t3); log(t3); + checkLog("getInt:1:1:getString:s:s:getUnit:():()") + + log(m1); log(m1) + log(m2); log(m2) + log(m3); log(m3) + checkLog("getInt:2:getInt:2:getString:ss:getString:ss:getUnit:():():():getUnit:():():()") + + log(vl1); log(vl1) + log(vl2); log(vl2) + log(vl3); log(vl3) + checkLog("2:2:ss:ss:():()") + + log(vr1); log(vr1); vr1 = 393; log(vr1) + log(vr2); log(vr2); vr2 = "h"; log(vr2) + log(vr3); log(vr3); vr3 = () ; log(vr3) + checkLog("2:2:393:ss:ss:h:():():()") + + log(lvl1); log(lvl1) + log(lvl2); log(lvl2) + log(lvl3); log(lvl3) + checkLog("getInt:2:2:getString:ss:ss:getUnit:():():():()") + } +} + +trait T { + def getInt : Int = { log("getInt"); 1 } + def getString: String = { log("getString"); "s" } + def getUnit : Unit = { log("getUnit") } + + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + checkLog("") + + def m1 = { + lazy val t1 = getInt + t1 + t1 + } + def m2 = { + lazy val t1 = getString + t1 + t1 + } + def m3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + val vl1 = { + lazy val t1 = getInt + t1 + t1 + } + val vl2 = { + lazy val t1 = getString + t1 + t1 + } + val vl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + var vr1 = { + lazy val t1 = getInt + t1 + t1 + } + var vr2 = { + lazy val t1 = getString + t1 + t1 + } + var vr3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("getInt:getString:getUnit:():()") + + + lazy val lvl1 = { + lazy val t1 = getInt + t1 + t1 + } + lazy val lvl2 = { + lazy val t1 = getString + t1 + t1 + } + lazy val lvl3 = { + lazy val t1 = getUnit + log(t1); log(t1) + } + checkLog("") + + + { + lazy val t1 = getInt + lazy val t2 = getString + lazy val t3 = getUnit + + log(t1 + t1) + log(t2 + t2) + log(t3); log(t3) + } + checkLog("getInt:2:getString:ss:getUnit:():()") + + + def run(): Unit = { + log(t1); log(t1); + log(t2); log(t2); + log(t3); log(t3); + checkLog("getInt:1:1:getString:s:s:getUnit:():()") + + log(m1); log(m1) + log(m2); log(m2) + log(m3); log(m3) + checkLog("getInt:2:getInt:2:getString:ss:getString:ss:getUnit:():():():getUnit:():():()") + + log(vl1); log(vl1) + log(vl2); log(vl2) + log(vl3); log(vl3) + checkLog("2:2:ss:ss:():()") + + log(vr1); log(vr1); vr1 = 393; log(vr1) + log(vr2); log(vr2); vr2 = "h"; log(vr2) + log(vr3); log(vr3); vr3 = () ; log(vr3) + checkLog("2:2:393:ss:ss:h:():():()") + + log(lvl1); log(lvl1) + log(lvl2); log(lvl2) + log(lvl3); log(lvl3) + checkLog("getInt:2:2:getString:ss:ss:getUnit:():():():()") + } +} + +class D extends T + +class D1 extends T { + override lazy val t1 = { log("o-t1"); -1 } + checkLog("") + + override def m1 = { log("o-m1"); -2 } + override val m2 = { log("o-m2"); "n" } + override lazy val m3 = { log("o-m3") } + checkLog("o-m2") + + override val vl1 = { log("o-vl1"); -3 } + checkLog("o-vl1") + + override lazy val lvl1 = { log("o-lvl1"); -4 } + checkLog("") + + override def run(): Unit = { + log(t1); log(t1) + checkLog("o-t1:-1:-1") + + log(m1); log(m1) + log(m2); log(m2) + log(m3); log(m3) + checkLog("o-m1:-2:o-m1:-2:n:n:o-m3:():()") + + log(vl1); log(vl1) + checkLog("-3:-3") + + log(lvl1); log(lvl1) + checkLog("o-lvl1:-4:-4") + } +} + +class E { + object T { log("init T"); override def toString = "T" } + def m = { object T { log("init T"); val x = 1 }; T.x } + checkLog("") +} + +object Test { + def main(args: Array[String]): Unit = { + val c = new C + c.run() + + val lzyComputeMethods = c.getClass.getDeclaredMethods.filter(_.getName contains "lzycompute").map(_.getName).toList.sorted + val expComputeMethods = List("lvl1$lzycompute", "lvl2$lzycompute", "lvl3$lzycompute", "t1$lzycompute", "t2$lzycompute", "t3$lzycompute") + assert( + lzyComputeMethods == expComputeMethods, + s"wrong lzycompute methods. expected:\n$expComputeMethods\nfound:\n$lzyComputeMethods") + + val fields = c.getClass.getDeclaredFields.toList.sortBy(_.getName).map(_.toString) + val expFields = List( + "private volatile byte C.bitmap$0", + "private int C.lvl1", + "private java.lang.String C.lvl2", + "private scala.runtime.BoxedUnit C.lvl3", + "private int C.t1", + "private java.lang.String C.t2", + "private scala.runtime.BoxedUnit C.t3", + "private final int C.vl1", + "private final java.lang.String C.vl2", + "private final scala.runtime.BoxedUnit C.vl3", + "private int C.vr1", + "private java.lang.String C.vr2", + "private scala.runtime.BoxedUnit C.vr3") + assert( + fields == expFields, + s"wrong fields. expected:\n$expFields\nfound:\n$fields") + + + val d = new D + d.run() + + val dFields = d.getClass.getDeclaredFields.toList.sortBy(_.getName).map(_.toString) + assert( + dFields == expFields.map(_.replaceAll(" C.", " D.")), + s"wrong fields. expected:\n$expFields\nfound:\n$fields") + + + val d1 = new D1 + d1.run() + + val e = new E + log(e.T); log(e.T) + checkLog("init T:T:T") + log(e.m); log(e.m) + checkLog("init T:1:init T:1") + } +} diff --git a/test/files/run/lazy_local_labels.check b/test/files/run/lazy_local_labels.check new file mode 100644 index 0000000000..e42c8fb8ce --- /dev/null +++ b/test/files/run/lazy_local_labels.check @@ -0,0 +1,9 @@ +HI +HI +HI +HI +HI +HI +HI +HI +HI diff --git a/test/files/run/lazy_local_labels.scala b/test/files/run/lazy_local_labels.scala new file mode 100644 index 0000000000..f4a1cdf689 --- /dev/null +++ b/test/files/run/lazy_local_labels.scala @@ -0,0 +1,28 @@ +// should print HI nine times to indicate the lazy val has been re-initialized on every iteration +object Test extends App { + def fooDo: Unit = { + var i = 3 + do { + lazy val x = { println("HI"); 1 } + i -= x + } while(i > 0) + } + + def fooWhile: Unit = { + var i = 3 + while(i > 0) { + lazy val x = { println("HI"); 1 } + i -= x + } + } + + @annotation.tailrec def fooTail(i: Int): Unit = { + lazy val x = { println("HI"); 1 } + if (i > 0) fooTail(i - x) + } + + + fooWhile + fooDo + fooTail(3) +} diff --git a/test/files/run/programmatic-main.check b/test/files/run/programmatic-main.check index 03f8273c17..cf8595db5d 100644 --- a/test/files/run/programmatic-main.check +++ b/test/files/run/programmatic-main.check @@ -10,18 +10,17 @@ superaccessors 6 add super accessors in traits and nested classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes - fields 11 synthesize accessors and fields + fields 11 synthesize accessors and fields, including bitmaps for la... tailcalls 12 replace tail calls by jumps specialize 13 @specialized-driven class and method specialization explicitouter 14 this refs to outer pointers erasure 15 erase types, add interfaces for traits posterasure 16 clean up erased inline classes - lazyvals 17 allocate bitmaps, translate lazy vals into lazified defs - lambdalift 18 move nested functions to top level - constructors 19 move field definitions into constructors - flatten 20 eliminate inner classes - mixin 21 mixin composition - cleanup 22 platform-specific cleanups, generate reflective calls - delambdafy 23 remove lambdas - jvm 24 generate JVM bytecode - terminal 25 the last phase during a compilation run + lambdalift 17 move nested functions to top level + constructors 18 move field definitions into constructors + flatten 19 eliminate inner classes + mixin 20 mixin composition + cleanup 21 platform-specific cleanups, generate reflective calls + delambdafy 22 remove lambdas + jvm 23 generate JVM bytecode + terminal 24 the last phase during a compilation run diff --git a/test/files/run/t3569.check b/test/files/run/t3569.check index a9fb5ff32e..e0e1d6c405 100644 --- a/test/files/run/t3569.check +++ b/test/files/run/t3569.check @@ -2,6 +2,8 @@ private final int Test$X.val1 private final int Test$X.val2 private final int Test$X.val3 +private int Test$X.const1 +private int Test$X.const2 private int Test$X.lval1 private int Test$X.lval2 private int Test$X.lval3 diff --git a/test/files/run/t3569.scala b/test/files/run/t3569.scala index eb3b424439..7da4de9e95 100644 --- a/test/files/run/t3569.scala +++ b/test/files/run/t3569.scala @@ -4,7 +4,13 @@ object Test { lazy val lv = scala.util.Random.nextInt() - class X(final var x: Int) { + trait T { final lazy val const1 = 1 } // no fields + + class X(final var x: Int) extends T { + // a lazy val does not receive a constant type, for backwards compat (e.g. for the repl) + // besides, since you explicitly wanted something lazy, we'll give you something lazy! (a field and a bitmap) + final lazy val const2 = 2 + final var var1: Int = 0 final private var var2: Int = 0 final private[this] var var3: Int = 0 diff --git a/test/files/run/t5552.check b/test/files/run/t5552.check index a19a60840e..73ad9cf824 100644 --- a/test/files/run/t5552.check +++ b/test/files/run/t5552.check @@ -1,2 +1,6 @@ +lazy: 3 (3,3) +(3,3) +lazy: 3.0 +(3.0,3.0) (3.0,3.0) diff --git a/test/files/run/t5552.scala b/test/files/run/t5552.scala index afb8a1f0be..5b717f9e13 100644 --- a/test/files/run/t5552.scala +++ b/test/files/run/t5552.scala @@ -1,10 +1,14 @@ class C[@specialized(Int) A](a:A) { - lazy val b = (a, a) + lazy val b = {println(s"lazy: $a"); (a, a)} // there should only be two instances of "lazy" in the output def c = b } object Test { def main(args:Array[String]) { - println(new C(3).c) - println(new C(3.0).c) + val cInt = new C(3) + println(cInt.c) + println(cInt.c) + val cFloat = new C(3.0) + println(cFloat.c) + println(cFloat.c) } } diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index 532d177300..c2e3ca58d8 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -54,7 +54,7 @@ package { def $outer(): T = MethodLocalObject$2.this.$outer; def $outer(): T = MethodLocalObject$2.this.$outer }; - final private[this] def MethodLocalObject$lzycompute$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { + final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { T.this.synchronized({ if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); @@ -62,10 +62,6 @@ package { }); MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; - final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1) - else - MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type](); @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { def ($outer: T, tryyParam$1: Int, tryyLocal$1: runtime.IntRef): <$anon: Function0> = { $anonfun$tryy$1.super.(); diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala index 56da0e2493..eae5385147 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -105,7 +105,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { ("x4$(LT;)I", MethodInlineInfo(true ,false,false)), ("x5()I", MethodInlineInfo(true, false,false)), ("x5$(LT;)I", MethodInlineInfo(true ,false,false)), - ("L$lzycompute$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)), ("L$1(Lscala/runtime/VolatileObjectRef;)LT$L$2$;", MethodInlineInfo(true, false,false)), ("nest$1()I", MethodInlineInfo(true, false,false)), ("$init$(LT;)V", MethodInlineInfo(true,false,false))), @@ -118,7 +117,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { val infoC = inlineInfo(c) val expectC = InlineInfo(false, None, Map( "O()LT$O$;" -> MethodInlineInfo(true ,false,false), - "O$lzycompute()LT$O$;" -> MethodInlineInfo(true, false,false), "f6()I" -> MethodInlineInfo(false,false,false), "x1()I" -> MethodInlineInfo(false,false,false), "T$_setter_$x1_$eq(I)V" -> MethodInlineInfo(false,false,false), @@ -181,7 +179,6 @@ class ScalaInlineInfoTest extends BytecodeTesting { val infoC = inlineInfo(c) val expected = Map( "()V" -> MethodInlineInfo(false,false,false), - "O$lzycompute()LC$O$;" -> MethodInlineInfo(true,false,false), "O()LC$O$;" -> MethodInlineInfo(true,false,false)) assert(infoC.methodInfos == expected, mapDiff(infoC.methodInfos, expected)) assertSameMethods(c, expected.keySet) -- cgit v1.2.3 From 0f813a5675f7f6c45acfab705b697e9cc1eceff3 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 19 Aug 2016 11:43:12 -0700 Subject: Move AccessorSynthesis out to its own file --- .../tools/nsc/transform/AccessorSynthesis.scala | 464 +++++++++++++++++++++ src/compiler/scala/tools/nsc/transform/Mixin.scala | 457 +------------------- 2 files changed, 466 insertions(+), 455 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala 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..c3bcf69205 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala @@ -0,0 +1,464 @@ +/* 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$ = + * 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) + + val synchedStats = storeRes :: mkSetFlag(lazyAccessor) :: Nil + val slowPathRhs = + Block(List(gen.mkSynchronizedCheck(thisRef, mkTest(lazyAccessor), synchedStats, 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(mkTest(lazyAccessor), 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/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 190755ff53..21f585ef55 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,461 +10,7 @@ package transform import symtab._ import Flags._ import scala.annotation.tailrec -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$ = - * 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) - - val synchedStats = storeRes :: mkSetFlag(lazyAccessor) :: Nil - val slowPathRhs = - Block(List(gen.mkSynchronizedCheck(thisRef, mkTest(lazyAccessor), synchedStats, 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(mkTest(lazyAccessor), 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)) - } - } - } -} abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis { import global._ -- cgit v1.2.3 From 6f7bd990ae69d6796c68894133c1975bef354e12 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 19 Aug 2016 20:01:38 -0700 Subject: Ensure access from subclass to trait lazy val Since we need to refer to a trait lazy val's accessor using a super call in a subclass (when the field and bitmap are added), we must ensure that access is allowed. If the lazy val has an access boundary (e.g., `private[somePkg]`), make sure the `PROTECTED` flag is set, which widens access to `protected[somePkg]`. (As `member.hasAccessBoundary` implies `!member.hasFlag(PRIVATE)`, we don't have to `resetFlag PRIVATE`.) --- src/compiler/scala/tools/nsc/transform/Fields.scala | 5 +++++ test/files/pos/trait_lazy_accessboundary.scala | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 test/files/pos/trait_lazy_accessboundary.scala diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 0b8705948c..cf8c25afb6 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -289,6 +289,11 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // 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.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. diff --git a/test/files/pos/trait_lazy_accessboundary.scala b/test/files/pos/trait_lazy_accessboundary.scala new file mode 100644 index 0000000000..6529816ffb --- /dev/null +++ b/test/files/pos/trait_lazy_accessboundary.scala @@ -0,0 +1,2 @@ +package foo { trait HasLazy { private[foo] lazy val myLazy = "my lady" } } +package bar { class MixInSuperLazy extends foo.HasLazy } -- cgit v1.2.3 From d9974be376e23f2a41373cd85977732aee6761a1 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 22 Aug 2016 11:43:49 -0700 Subject: Double-checked locking for modules. Inline `mkSynchronizedCheck`, whose abstraction obscured rather than clarified. --- src/compiler/scala/tools/nsc/ast/TreeGen.scala | 21 +----------- .../tools/nsc/transform/AccessorSynthesis.scala | 11 ++++--- .../scala/tools/nsc/transform/Fields.scala | 37 +++++++++++++++------- test/files/run/delambdafy_t6028.check | 14 +++++--- test/files/run/t6028.check | 14 +++++--- 5 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index ac47b3c464..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,25 +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 `attrThis`. 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 - * - * TODO: update to state of the art on java 8 (https://shipilev.net/blog/2014/safe-public-construction/) - */ - 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/transform/AccessorSynthesis.scala b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala index c3bcf69205..120ee5c26e 100644 --- a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala +++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala @@ -329,7 +329,7 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL { 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) + Select(thisRef, sym.accessedOrSelf) === gen.mkAsInstanceOf(NULL, sym.info.resultType) val nulls = nullables.getOrElse(lazyAccessor, Nil) map nullify @@ -343,13 +343,14 @@ trait AccessorSynthesis extends Transform with ast.TreeDSL { val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) - val synchedStats = storeRes :: mkSetFlag(lazyAccessor) :: Nil - val slowPathRhs = - Block(List(gen.mkSynchronizedCheck(thisRef, mkTest(lazyAccessor), synchedStats, nulls)), selectVar) + 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(mkTest(lazyAccessor), Apply(Select(thisRef, slowPathSym), Nil), selectVar) + 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)) diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index cf8c25afb6..217c4cb5e3 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -189,10 +189,29 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // for local modules, we synchronize on the owner of the method that owns the module val monitorHolder = This(moduleVar.owner.enclClass) - val cond = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL)) + def needsInit = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL)) val init = Assign(moduleVarRef, gen.newModule(module, moduleVar.info)) - Block(List(gen.mkSynchronizedCheck(monitorHolder, cond, List(init), Nil)), moduleVarRef) + /** 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 @@ -523,16 +542,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor val valueSetter = if (isUnit) NoSymbol else valueGetter.setterIn(refClass) val setValue = if (isUnit) rhs else Apply(Select(Ident(holderSym), valueSetter), rhs :: Nil) val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) - gen.mkSynchronized(Ident(holderSym), - Block(List( - If(NOT(Ident(holderSym) DOT initializedGetter), - Block(List( - setInitialized), - setValue), - EmptyTree)), - getValue)) // must read the value within the synchronized block since it's not volatile + + // must read the value within the synchronized block since it's not volatile // (there's no happens-before relation with the read of the volatile initialized field) - // TODO: double-checked locking + // TODO: double-checked locking (https://github.come/scala/scala-dev/issues/204) + gen.mkSynchronized(Ident(holderSym))( + If(Ident(holderSym) DOT initializedGetter, getValue, Block(List(setValue, setInitialized), getValue))) } // do last! diff --git a/test/files/run/delambdafy_t6028.check b/test/files/run/delambdafy_t6028.check index b188928c09..7bd68c78e9 100644 --- a/test/files/run/delambdafy_t6028.check +++ b/test/files/run/delambdafy_t6028.check @@ -43,11 +43,15 @@ package { def $outer(): T = MethodLocalObject$2.this.$outer }; final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { - T.this.synchronized({ - if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); - scala.runtime.BoxedUnit.UNIT - }); + if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) + { + T.this.synchronized({ + if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) + MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); + scala.runtime.BoxedUnit.UNIT + }); + () + }; MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; final private[this] def $anonfun$tryy$1(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try { diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index c2e3ca58d8..f757bc93ff 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -55,11 +55,15 @@ package { def $outer(): T = MethodLocalObject$2.this.$outer }; final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { - T.this.synchronized({ - if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); - scala.runtime.BoxedUnit.UNIT - }); + if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) + { + T.this.synchronized({ + if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) + MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); + scala.runtime.BoxedUnit.UNIT + }); + () + }; MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { -- cgit v1.2.3 From 708482126167b09139ded6143aab1a9f272200fa Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 22 Aug 2016 14:52:41 -0700 Subject: Double-checked locking for local lazy vals. --- .../scala/tools/nsc/transform/Fields.scala | 63 ++++++++++++---------- test/files/run/lazy-locals-2.scala | 2 +- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 217c4cb5e3..8f2fef6469 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -513,43 +513,52 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor * Desugar a local `lazy val x: Int = rhs` into * ``` * val x$lzy = new scala.runtime.LazyInt() - * def x(): Int = + * def x$lzycompute(): Int = * x$lzy.synchronized { - * if (!x$lzy.initialized) { + * if (x$lzy.initialized()) x$lzy.value() + * else { * x$lzy.initialized = true * x$lzy.value = rhs * } - * x$lzy.value * } + * def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute() * ``` */ private def mkLazyLocalDef(lazyVal: Symbol, rhs: Tree): Tree = { + import CODE._ + 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 flags = (lazyVal.flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) // TODO: why include MUTABLE??? - val name = lazyVal.name.toTermName.append(nme.LAZY_LOCAL_SUFFIX_STRING) - val holderSym = - lazyVal.owner.newValue(name, lazyVal.pos, flags) setInfo refTpe - - val accessor = mkAccessor(lazyVal) { - import CODE._ - val initializedGetter = refTpe.member(nme.initialized) - val setInitialized = Apply(Select(Ident(holderSym), initializedGetter.setterIn(refClass)), TRUE :: Nil) - val isUnit = refClass == LazyUnitClass - val valueGetter = if (isUnit) NoSymbol else refTpe.member(nme.value) - val valueSetter = if (isUnit) NoSymbol else valueGetter.setterIn(refClass) - val setValue = if (isUnit) rhs else Apply(Select(Ident(holderSym), valueSetter), rhs :: Nil) - val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) - - // must read the value within the synchronized block since it's not volatile - // (there's no happens-before relation with the read of the volatile initialized field) - // TODO: double-checked locking (https://github.come/scala/scala-dev/issues/204) - gen.mkSynchronized(Ident(holderSym))( - If(Ident(holderSym) DOT initializedGetter, getValue, Block(List(setValue, setInitialized), getValue))) + 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 + val flags = (lazyVal.flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) // TODO: why include MUTABLE??? + val holderSym = owner.newValue(lazyName append nme.LAZY_LOCAL_SUFFIX_STRING, pos, flags) setInfo refTpe + + val initializedGetter = refTpe.member(nme.initialized) + val isUnit = refClass == LazyUnitClass + val valueGetter = if (isUnit) NoSymbol else refTpe.member(nme.value) + val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) + + def mkChecked(res: Tree) = If(Ident(holderSym) DOT initializedGetter, getValue, res) + + 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) + val setValue = + if (isUnit) rhsAtComputer + else Apply(Select(Ident(holderSym), valueGetter.setterIn(refClass)), rhsAtComputer :: Nil) + + gen.mkSynchronized(Ident(holderSym))(mkChecked(Block(List(setValue, setInitialized), getValue))) } + val accessor = mkAccessor(lazyVal)(mkChecked(Apply(Ident(computerSym), Nil))) + // do last! // remove LAZY: prevent lazy expansion in mixin // remove STABLE: prevent replacing accessor call of type Unit by BoxedUnit.UNIT in erasure @@ -557,7 +566,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // lifted into a trait (TODO: not sure about the details here) lazyVal.resetFlag(LAZY | STABLE | ACCESSOR) - Thicket(mkField(holderSym, New(refTpe)) :: accessor :: Nil) + Thicket(mkField(holderSym, New(refTpe)) :: computer :: accessor :: Nil) } // synth trees for accessors/fields and trait setters when they are mixed into a class diff --git a/test/files/run/lazy-locals-2.scala b/test/files/run/lazy-locals-2.scala index cb905d3bef..d6c33cffcb 100644 --- a/test/files/run/lazy-locals-2.scala +++ b/test/files/run/lazy-locals-2.scala @@ -276,7 +276,7 @@ object Test { c.run() val lzyComputeMethods = c.getClass.getDeclaredMethods.filter(_.getName contains "lzycompute").map(_.getName).toList.sorted - val expComputeMethods = List("lvl1$lzycompute", "lvl2$lzycompute", "lvl3$lzycompute", "t1$lzycompute", "t2$lzycompute", "t3$lzycompute") + val expComputeMethods = List("lvl1$lzycompute", "lvl2$lzycompute", "lvl3$lzycompute", "t1$lzycompute", "t1$lzycompute$1", "t1$lzycompute$10", "t1$lzycompute$11", "t1$lzycompute$12", "t1$lzycompute$13", "t1$lzycompute$2", "t1$lzycompute$3", "t1$lzycompute$4", "t1$lzycompute$5", "t1$lzycompute$6", "t1$lzycompute$7", "t1$lzycompute$8", "t1$lzycompute$9", "t2$lzycompute", "t2$lzycompute$1", "t3$lzycompute", "t3$lzycompute$1") assert( lzyComputeMethods == expComputeMethods, s"wrong lzycompute methods. expected:\n$expComputeMethods\nfound:\n$lzyComputeMethods") -- cgit v1.2.3 From f12e6257eef36ffaa9e2a72afc3f83f59296bf67 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 23 Aug 2016 10:59:23 -0700 Subject: Local lazy vals: avoid boxing and mutable capture --- .../scala/tools/nsc/transform/Fields.scala | 45 +++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 8f2fef6469..6753016b9e 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -507,7 +507,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // 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 mkField(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef] + def mkTypedValDef(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef] /** * Desugar a local `lazy val x: Int = rhs` into @@ -526,6 +526,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor */ 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 @@ -534,15 +535,21 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor val lazyName = lazyVal.name.toTermName val pos = lazyVal.pos.focus - val flags = (lazyVal.flags & FieldFlags | ARTIFACT | MUTABLE) & ~(IMPLICIT | STABLE) // TODO: why include MUTABLE??? - val holderSym = owner.newValue(lazyName append nme.LAZY_LOCAL_SUFFIX_STRING, pos, flags) setInfo refTpe + + // 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) - val getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) + def getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueGetter), Nil) - def mkChecked(res: Tree) = If(Ident(holderSym) DOT initializedGetter, getValue, res) + 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) @@ -550,14 +557,26 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor val rhsAtComputer = rhs.changeOwner(lazyVal -> computerSym) val computer = mkAccessor(computerSym) { val setInitialized = Apply(Select(Ident(holderSym), initializedGetter.setterIn(refClass)), TRUE :: Nil) - val setValue = - if (isUnit) rhsAtComputer - else Apply(Select(Ident(holderSym), valueGetter.setterIn(refClass)), rhsAtComputer :: Nil) - gen.mkSynchronized(Ident(holderSym))(mkChecked(Block(List(setValue, setInitialized), getValue))) + if (isUnit) + gen.mkSynchronized(Ident(holderSym))(mkChecked(alreadyComputed = UNIT, compute = Block(List(rhsAtComputer, setInitialized), UNIT))) + else { + // we write to a local var outside of the synchronized block to avoid boxing/unboxing (synchronized method is polymorphic) + val resVarSym = computerSym.newVariable(localLazyName, pos, ARTIFACT) setInfo lazyValType + + val alreadyComputed = Assign(Ident(resVarSym), getValue) + val storeComputed = Apply(Select(Ident(holderSym), valueGetter.setterIn(refClass)), Ident(resVarSym) :: Nil) + val compute = Block(List(Assign(Ident(resVarSym), rhsAtComputer), storeComputed), setInitialized) + + Block( + mkTypedValDef(resVarSym) :: + gen.mkSynchronized(Ident(holderSym))(mkChecked(alreadyComputed, compute)) :: Nil, + Ident(resVarSym) + ) + } } - val accessor = mkAccessor(lazyVal)(mkChecked(Apply(Ident(computerSym), Nil))) + val accessor = mkAccessor(lazyVal)(mkChecked(getValue, Apply(Ident(computerSym), Nil))) // do last! // remove LAZY: prevent lazy expansion in mixin @@ -566,7 +585,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // lifted into a trait (TODO: not sure about the details here) lazyVal.resetFlag(LAZY | STABLE | ACCESSOR) - Thicket(mkField(holderSym, New(refTpe)) :: computer :: accessor :: Nil) + Thicket(mkTypedValDef(holderSym, New(refTpe)) :: computer :: accessor :: Nil) } // synth trees for accessors/fields and trait setters when they are mixed into a class @@ -623,7 +642,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor 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 } } @@ -685,7 +704,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (clazz.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) + Thicket(cd :: mkTypedValDef(moduleVar) :: mkAccessor(statSym)(moduleInit(statSym)) :: Nil) } case tree => -- cgit v1.2.3 From 92d1af11b04b4f7c8aafd4ff911bf747eb1029aa Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 26 Aug 2016 09:21:38 +0200 Subject: Specialize erasure of `synchronized` primitive method The goal is to avoid emitting unneeded `BoxedUnit` values, which are the result of adapting a `Unit`-typed expression inside a `synchronized(...)` to the erased type of `synchronized`'s argument -- `Object`. The proposed solution gives `synchronized` a polymorphic type (the info of the type param is still erased so that bounds checking works in the erased type system), so that an application `synchronized(println("boo"))` erases to `synchronized[Unit])(println("boo"))`, and no boxing is performed on the `println("boo")` argument, whose expected type is now `Unit` instead of `Object`. --- .../scala/tools/nsc/transform/CleanUp.scala | 5 ++++ .../scala/tools/nsc/transform/Erasure.scala | 5 ++-- .../scala/reflect/internal/transform/Erasure.scala | 33 ++++++++++++++-------- test/files/run/delambdafy_t6028.check | 10 ++----- test/files/run/t6028.check | 10 ++----- 5 files changed, 33 insertions(+), 30 deletions(-) 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[...]), )` // with just `ArrayValue(...).$asInstanceOf[...]` // 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/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index a9e26c0f7d..e2f1e74740 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -342,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]. : {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]. --> {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)), @@ -375,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/test/files/run/delambdafy_t6028.check b/test/files/run/delambdafy_t6028.check index 7bd68c78e9..eaba70ee1a 100644 --- a/test/files/run/delambdafy_t6028.check +++ b/test/files/run/delambdafy_t6028.check @@ -44,14 +44,8 @@ package { }; final private[this] def MethodLocalObject$1(barParam$1: String, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - { - T.this.synchronized({ - if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); - scala.runtime.BoxedUnit.UNIT - }); - () - }; + T.this.synchronized[Unit](if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) + MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1)); MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; final private[this] def $anonfun$tryy$1(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try { diff --git a/test/files/run/t6028.check b/test/files/run/t6028.check index f757bc93ff..d6cc452bbf 100644 --- a/test/files/run/t6028.check +++ b/test/files/run/t6028.check @@ -56,14 +56,8 @@ package { }; final private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - { - T.this.synchronized({ - if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) - MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); - scala.runtime.BoxedUnit.UNIT - }); - () - }; + T.this.synchronized[Unit](if (MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]().eq(null)) + MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1)); MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() }; @SerialVersionUID(value = 0) final class $anonfun$tryy$1 extends scala.runtime.AbstractFunction0$mcV$sp with Serializable { -- cgit v1.2.3 From c0763b05dac2e3d12301e828f4f1aaf83b4e41ac Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 31 Aug 2016 16:39:24 +0200 Subject: Cleanups after integrating lazyvals into fields. Mostly refactorings and catching up with doc updates. Some changes to flag handling, removing some redundancy, and making lazy fields and modules a bit more consistent in their flags. They now uniformly carry LAZY or MODULEVAR. Before, LAZY was dropped because mixin had some lazy val logic. No longer. --- .../scala/tools/nsc/transform/Fields.scala | 131 +++++++++++---------- src/compiler/scala/tools/nsc/transform/Mixin.scala | 12 +- test/files/neg/t6666.check | 4 +- 3 files changed, 78 insertions(+), 69 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 6753016b9e..3403c35152 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,7 +66,10 @@ 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 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 with AccessorSynthesis { import global._ @@ -68,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? @@ -172,19 +187,29 @@ 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] + + // 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, extraFlags: Long): TermSymbol = { + private def newModuleVarSymbol(owner: Symbol, module: Symbol, tp: Type): TermSymbol = { // println(s"new module var in $site for $module of type $tp") - val moduleVar = owner.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) + val moduleVar = moduleOrLazyVarOf(module) def moduleVarRef = gen.mkAttributedRef(moduleVar) // for local modules, we synchronize on the owner of the method that owns the module @@ -215,28 +240,19 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } // NoSymbol for lazy accessor sym with unit result type - def lazyVarOf(sym: Symbol) = moduleVarOf.getOrElse(sym, NoSymbol) + def lazyVarOf(sym: Symbol) = moduleOrLazyVarOf.getOrElse(sym, NoSymbol) - private def newLazyVarSymbol(owner: Symbol, member: Symbol, tp: Type, extraFlags: Long = 0): TermSymbol = { - val flags = member.flags | extraFlags - val pos = member.pos + 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) - // the underlying field for a lazy val should not be final because we write to it outside of a constructor, - // so, set the MUTABLE flag - val fieldFlags = flags & FieldFlags | PrivateLocal | MUTABLE + // 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 - val sym = owner.newValue(name, pos.focus, fieldFlags | extraFlags) setInfo tp - moduleVarOf(member) = sym + moduleOrLazyVarOf(member) = sym sym } - private def lazyValInit(member: Symbol, rhs: Tree) = { - val lazyVar = moduleVarOf(member) - assert(lazyVar.isMutable, lazyVar) - gen.mkAssignAndReturn(lazyVar, rhs) - } - private object synthFieldsAndAccessors extends TypeMap { private def newTraitSetter(getter: Symbol, clazz: Symbol) = { // Add setter for an immutable, memoizing getter @@ -256,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) @@ -287,9 +303,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor lazyCallingSuper setInfo tp } - // make sure they end up final in bytecode - final private val fieldFlags = PrivateLocal | FINAL | SYNTHETIC | NEEDS_TREES - 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) @@ -359,10 +372,10 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } def newModuleVarMember(member: Symbol): TermSymbol = - newModuleVarSymbol(clazz, member, site.memberType(member).resultType, fieldFlags) + newModuleVarSymbol(clazz, member, site.memberType(member).resultType) def newLazyVarMember(member: Symbol): TermSymbol = - newLazyVarSymbol(clazz, member, site.memberType(member).resultType, fieldFlags) + 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 @@ -491,9 +504,8 @@ 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) with CheckedAccessorTreeSynthesis { protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) @@ -579,11 +591,10 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor val accessor = mkAccessor(lazyVal)(mkChecked(getValue, Apply(Ident(computerSym), Nil))) // do last! - // remove LAZY: prevent lazy expansion in mixin // 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(LAZY | STABLE | ACCESSOR) + lazyVal.resetFlag(STABLE | ACCESSOR) Thicket(mkTypedValDef(holderSym, New(refTpe)) :: computer :: accessor :: Nil) } @@ -651,7 +662,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner))) override def transform(stat: Tree): Tree = { - val clazz = currentOwner + val currOwner = currentOwner // often a class, but not necessarily val statSym = stat.symbol /* @@ -674,36 +685,36 @@ 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).constantTyped => + && !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, clazz) => + 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 (clazz.isTrait) mkAccessor(statSym)(transformedRhs) - else if (!clazz.isClass) mkLazyLocalDef(vd.symbol, transformedRhs) + 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(clazz) - synthAccessorInClass.expandLazyClassMember(lazyVarOf(statSym), statSym, transformedRhs, nullables.getOrElse(clazz, Map.empty)) + 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).constantTyped => + && 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) + val moduleVar = newModuleVarSymbol(currOwner, statSym, statSym.info.resultType) Thicket(cd :: mkTypedValDef(moduleVar) :: mkAccessor(statSym)(moduleInit(statSym)) :: Nil) } diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 21f585ef55..d462b00261 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -71,15 +71,15 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthes * (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 @@ -181,9 +181,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthes 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) } @@ -407,7 +405,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthes if (clazz.isTrait || sym.isSuperAccessor) addDefDef(sym) // implement methods mixed in from a supertrait (the symbols were created by mixinTraitMembers) else if (sym.hasFlag(ACCESSOR) && !sym.hasFlag(DEFERRED)) { - assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/param?!?") + assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not param?!?") // add accessor definitions addDefDef(sym, accessorBody(sym)) diff --git a/test/files/neg/t6666.check b/test/files/neg/t6666.check index 090ef72770..bae948fe56 100644 --- a/test/files/neg/t6666.check +++ b/test/files/neg/t6666.check @@ -1,7 +1,7 @@ t6666.scala:23: error: Implementation restriction: access of method x$2 in object O1 from <$anon: Function0>, would require illegal premature access to object O1 F.byname(x) ^ -t6666.scala:30: error: Implementation restriction: access of method x$3 in object O2 from <$anon: Function0>, would require illegal premature access to object O2 +t6666.scala:30: error: Implementation restriction: access of lazy value x$3 in object O2 from <$anon: Function0>, would require illegal premature access to object O2 F.byname(x) ^ t6666.scala:37: error: Implementation restriction: access of method x$4 in object O3 from <$anon: Function0>, would require illegal premature access to object O3 @@ -10,7 +10,7 @@ t6666.scala:37: error: Implementation restriction: access of method x$4 in objec t6666.scala:50: error: Implementation restriction: access of method x$6 in class C1 from <$anon: Function0>, would require illegal premature access to the unconstructed `this` of class C1 F.byname(x) ^ -t6666.scala:54: error: Implementation restriction: access of method x$7 in class C2 from <$anon: Function0>, would require illegal premature access to the unconstructed `this` of class C2 +t6666.scala:54: error: Implementation restriction: access of lazy value x$7 in class C2 from <$anon: Function0>, would require illegal premature access to the unconstructed `this` of class C2 F.byname(x) ^ t6666.scala:58: error: Implementation restriction: access of method x$8 in class C3 from <$anon: Function0>, would require illegal premature access to the unconstructed `this` of class C3 -- cgit v1.2.3 From 743f0d2c2a33bc8ab0c1f7bee796865672ba2fcc Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 31 Aug 2016 16:54:11 +0200 Subject: Lazy val without local. Now `synchronized` is erased specially to avoid boxing, we can drop that work around. Note that this does add an extra cast and getter call on the slow path, but that likely doesn't matter. ``` class C { def foo = {lazy val x = {println("a"); "A" }; x } } ``` becomes ``` def foo(): String = { lazy val x$lzy: scala.runtime.LazyRef[String] = new scala.runtime.LazyRef[String](); private def x$lzycompute(): String = x$lzy.synchronized[String]{ if (x$lzy.initialized()) x$lzy.value() // NOTE: gets an `.asInstanceOf[String]` after erasure else { x$lzy.value_=({ scala.Predef.println("a"); "A" }); x$lzy.initialized_=(true); x$lzy.value() // NOTE: gets an `.asInstanceOf[String]` after erasure } } lazy def x(): String = if (x$lzy.initialized()) x$lzy.value() // NOTE: gets an `.asInstanceOf[String]` after erasure else x$lzycompute(); x() } ``` --- src/compiler/scala/tools/nsc/transform/Fields.scala | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 3403c35152..3529fbd0eb 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -573,17 +573,11 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (isUnit) gen.mkSynchronized(Ident(holderSym))(mkChecked(alreadyComputed = UNIT, compute = Block(List(rhsAtComputer, setInitialized), UNIT))) else { - // we write to a local var outside of the synchronized block to avoid boxing/unboxing (synchronized method is polymorphic) - val resVarSym = computerSym.newVariable(localLazyName, pos, ARTIFACT) setInfo lazyValType + val valueSetter = Select(Ident(holderSym), valueGetter.setterIn(refClass)) - val alreadyComputed = Assign(Ident(resVarSym), getValue) - val storeComputed = Apply(Select(Ident(holderSym), valueGetter.setterIn(refClass)), Ident(resVarSym) :: Nil) - val compute = Block(List(Assign(Ident(resVarSym), rhsAtComputer), storeComputed), setInitialized) - - Block( - mkTypedValDef(resVarSym) :: - gen.mkSynchronized(Ident(holderSym))(mkChecked(alreadyComputed, compute)) :: Nil, - Ident(resVarSym) + gen.mkSynchronized(Ident(holderSym))(mkChecked( + alreadyComputed = getValue, + compute = Block(Apply(valueSetter, rhsAtComputer :: Nil) :: setInitialized :: Nil, getValue)) ) } } -- cgit v1.2.3