Skip to content

Constructors: fixes to maintain needed private fields. #774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 14, 2015
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Compiler {
new LinkScala2ImplClasses,
new NonLocalReturns,
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here
new Constructors,
new Constructors, // constructors changes decls in transformTemplate, no InfoTransformers should be added after it
new FunctionalInterfaces,
new GetClass), // getClass transformation should be applied to specialized methods
List(new LambdaLift, // in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here
Expand Down
106 changes: 64 additions & 42 deletions src/dotty/tools/dotc/transform/Constructors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,58 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize])


// Collect all private parameter accessors and value definitions that need
// to be retained. There are several reasons why a parameter accessor or
// definition might need to be retained:
// 1. It is accessed after the constructor has finished
// 2. It is accessed before it is defined
// 3. It is accessed on an object other than `this`
// 4. It is a mutable parameter accessor
// 5. It is has a wildcard initializer `_`
private val retainedPrivateVals = mutable.Set[Symbol]()
private val seenPrivateVals = mutable.Set[Symbol]()

private def markUsedPrivateSymbols(tree: RefTree)(implicit ctx: Context): Unit = {

val sym = tree.symbol
def retain =
retainedPrivateVals.add(sym)

if (mightBeDropped(sym) && sym.owner.isClass) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd reverse the order of the two conditions in the &&. mightBeDropped makes sense only for class members.

val owner = sym.owner.asClass

tree match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of this match is not used anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this match returns Unit. How could it be used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, I missed that retain had side-effects, I suggest replacing def retain by def retain() and using retain() to follow the scala style.

case Ident(_) | Select(This(_), _) =>
def inConstructor = {
val method = ctx.owner.enclosingMethod
method.isPrimaryConstructor && ctx.owner.enclosingClass == owner
}
if (inConstructor && (sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended this block contains comment on why nothing happens here

// used inside constructor, accessed on this,
// could use constructor argument instead, no need to retain field
}
else retain
case _ => retain
}
}
}

override def transformIdent(tree: tpd.Ident)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
markUsedPrivateSymbols(tree)
tree
}

override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
markUsedPrivateSymbols(tree)
tree
}

override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
if (mightBeDropped(tree.symbol))
(if (isWildcardStarArg(tree.rhs)) retainedPrivateVals else seenPrivateVals) += tree.symbol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the isWildcardStarArg test? I see it is copied from the previous version, which is copied from scalac's version. Just wondered if you figured out the reason for the test.

tree
}

/** All initializers for non-lazy fields should be moved into constructor.
* All non-abstract methods should be implemented (this is assured for constructors
* in this phase and for other methods in memoize).
Expand Down Expand Up @@ -75,6 +127,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor

override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
val cls = ctx.owner.asClass

val constr @ DefDef(nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr

// Produce aligned accessors and constructor parameters. We have to adjust
Expand Down Expand Up @@ -113,46 +166,9 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
}
}

// Collect all private parameter accessors and value definitions that need
// to be retained. There are several reasons why a parameter accessor or
// definition might need to be retained:
// 1. It is accessed after the constructor has finished
// 2. It is accessed before it is defined
// 3. It is accessed on an object other than `this`
// 4. It is a mutable parameter accessor
// 5. It is has a wildcard initializer `_`
object usage extends TreeTraverser {
private var inConstr: Boolean = true
private val seen = mutable.Set[Symbol](accessors: _*)
val retained = mutable.Set[Symbol]()
def dropped: collection.Set[Symbol] = seen -- retained
override def traverse(tree: Tree)(implicit ctx: Context) = {
val sym = tree.symbol
tree match {
case Ident(_) | Select(This(_), _) if inConstr && seen(tree.symbol) =>
// could refer to definition in constructors, so no retention necessary
case tree: RefTree =>
if (mightBeDropped(sym)) retained += sym
case _ =>
}
if (!noDirectRefsFrom(tree)) traverseChildren(tree)
}
def collect(stats: List[Tree]): Unit = stats foreach {
case stat: ValDef if !stat.symbol.is(Lazy) =>
traverse(stat)
if (mightBeDropped(stat.symbol))
(if (isWildcardStarArg(stat.rhs)) retained else seen) += stat.symbol
case stat: DefTree =>
inConstr = false
traverse(stat)
inConstr = true
case stat =>
traverse(stat)
}
def isRetained(acc: Symbol) = {
!mightBeDropped(acc) || retainedPrivateVals(acc)
}
usage.collect(tree.body)

def isRetained(acc: Symbol) = !mightBeDropped(acc) || usage.retained(acc)

val constrStats, clsStats = new mutable.ListBuffer[Tree]

Expand All @@ -170,6 +186,8 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
}
}

val dropped = mutable.Set[Symbol]()

// Split class body into statements that go into constructor and
// definitions that are kept as members of the class.
def splitStats(stats: List[Tree]): Unit = stats match {
Expand All @@ -183,6 +201,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
clsStats += cpy.ValDef(stat)(rhs = EmptyTree)
}
else if (!stat.rhs.isEmpty) {
dropped += sym
sym.copySymDenotation(
initFlags = sym.flags &~ Private,
owner = constr.symbol).installAfter(thisTransform)
Expand All @@ -203,8 +222,10 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor

// The initializers for the retained accessors */
val copyParams = accessors flatMap { acc =>
if (!isRetained(acc)) Nil
else {
if (!isRetained(acc)) {
dropped += acc
Nil
} else {
val target = if (acc.is(Method)) acc.field else acc
if (!target.exists) Nil // this case arises when the parameter accessor is an alias
else {
Expand All @@ -224,12 +245,13 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
}

// Drop accessors that are not retained from class scope
val dropped = usage.dropped
if (dropped.nonEmpty) {
val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error
cls.copy(
info = clsInfo.derivedClassInfo(
decls = clsInfo.decls.filteredScope(!dropped.contains(_))))

// TODO: this happens to work only because Constructors is the last phase in group
}

val (superCalls, followConstrStats) = constrStats.toList match {
Expand Down
14 changes: 7 additions & 7 deletions src/dotty/tools/dotc/transform/Memoize.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ import Decorators._
lazy val field = sym.field.orElse(newField).asTerm
if (sym.is(Accessor, butNot = NoFieldNeeded))
if (sym.isGetter) {
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
if (isWildcardArg(rhs)) rhs = EmptyTree
val fieldDef = transformFollowing(ValDef(field, rhs))
val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field)))
Thicket(fieldDef, getterDef)
}
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
if (isWildcardArg(rhs)) rhs = EmptyTree
val fieldDef = transformFollowing(ValDef(field, rhs))
val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info))
Thicket(fieldDef, getterDef)
}
else if (sym.isSetter) {
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
val initializer = Assign(ref(field), ref(tree.vparamss.head.head.symbol))
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer))
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
}
else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as
// neither getters nor setters
Expand Down