Skip to content

Commit

Permalink
Fields phase expands lazy vals like modules
Browse files Browse the repository at this point in the history
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

- existential crisis averted?

don't skolemize in accessor body since all we're going
to do is deskolemize and hope we get to the same
existential (sometimes we don't, when quantifying values)

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
  • Loading branch information
adriaanm committed Aug 11, 2016
1 parent af02e29 commit be2c934
Show file tree
Hide file tree
Showing 40 changed files with 464 additions and 576 deletions.
36 changes: 4 additions & 32 deletions src/compiler/scala/reflect/reify/phases/Reshape.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,24 @@ 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)
case template @ Template(parents, self, body) =>
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)
Expand Down Expand Up @@ -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
Expand Down
138 changes: 113 additions & 25 deletions src/compiler/scala/tools/nsc/transform/Fields.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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()

Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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] = {
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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 = <rhs>; 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)
Expand All @@ -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)
Expand Down
Loading

0 comments on commit be2c934

Please sign in to comment.