Skip to content

Commit 4f2e912

Browse files
committed
Merge pull request #774 from dotty-staging/fix-constructors
Constructors: fixes to maintain needed private fields.
2 parents 7f01a79 + 863d72d commit 4f2e912

File tree

3 files changed

+72
-50
lines changed

3 files changed

+72
-50
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class Compiler {
7171
new LinkScala2ImplClasses,
7272
new NonLocalReturns,
7373
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here
74-
new Constructors,
74+
new Constructors, // constructors changes decls in transformTemplate, no InfoTransformers should be added after it
7575
new FunctionalInterfaces,
7676
new GetClass), // getClass transformation should be applied to specialized methods
7777
List(new LambdaLift, // in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here

src/dotty/tools/dotc/transform/Constructors.scala

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,58 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
3333
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize])
3434

3535

36+
// Collect all private parameter accessors and value definitions that need
37+
// to be retained. There are several reasons why a parameter accessor or
38+
// definition might need to be retained:
39+
// 1. It is accessed after the constructor has finished
40+
// 2. It is accessed before it is defined
41+
// 3. It is accessed on an object other than `this`
42+
// 4. It is a mutable parameter accessor
43+
// 5. It is has a wildcard initializer `_`
44+
private val retainedPrivateVals = mutable.Set[Symbol]()
45+
private val seenPrivateVals = mutable.Set[Symbol]()
46+
47+
private def markUsedPrivateSymbols(tree: RefTree)(implicit ctx: Context): Unit = {
48+
49+
val sym = tree.symbol
50+
def retain() =
51+
retainedPrivateVals.add(sym)
52+
53+
if (sym.exists && sym.owner.isClass && mightBeDropped(sym)) {
54+
val owner = sym.owner.asClass
55+
56+
tree match {
57+
case Ident(_) | Select(This(_), _) =>
58+
def inConstructor = {
59+
val method = ctx.owner.enclosingMethod
60+
method.isPrimaryConstructor && ctx.owner.enclosingClass == owner
61+
}
62+
if (inConstructor && (sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
63+
// used inside constructor, accessed on this,
64+
// could use constructor argument instead, no need to retain field
65+
}
66+
else retain()
67+
case _ => retain()
68+
}
69+
}
70+
}
71+
72+
override def transformIdent(tree: tpd.Ident)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
73+
markUsedPrivateSymbols(tree)
74+
tree
75+
}
76+
77+
override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
78+
markUsedPrivateSymbols(tree)
79+
tree
80+
}
81+
82+
override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
83+
if (mightBeDropped(tree.symbol))
84+
(if (isWildcardStarArg(tree.rhs)) retainedPrivateVals else seenPrivateVals) += tree.symbol
85+
tree
86+
}
87+
3688
/** All initializers for non-lazy fields should be moved into constructor.
3789
* All non-abstract methods should be implemented (this is assured for constructors
3890
* in this phase and for other methods in memoize).
@@ -75,6 +127,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
75127

76128
override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
77129
val cls = ctx.owner.asClass
130+
78131
val constr @ DefDef(nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr
79132

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

116-
// Collect all private parameter accessors and value definitions that need
117-
// to be retained. There are several reasons why a parameter accessor or
118-
// definition might need to be retained:
119-
// 1. It is accessed after the constructor has finished
120-
// 2. It is accessed before it is defined
121-
// 3. It is accessed on an object other than `this`
122-
// 4. It is a mutable parameter accessor
123-
// 5. It is has a wildcard initializer `_`
124-
object usage extends TreeTraverser {
125-
private var inConstr: Boolean = true
126-
private val seen = mutable.Set[Symbol](accessors: _*)
127-
val retained = mutable.Set[Symbol]()
128-
def dropped: collection.Set[Symbol] = seen -- retained
129-
override def traverse(tree: Tree)(implicit ctx: Context) = {
130-
val sym = tree.symbol
131-
tree match {
132-
case Ident(_) | Select(This(_), _) if inConstr && seen(tree.symbol) =>
133-
// could refer to definition in constructors, so no retention necessary
134-
case tree: RefTree =>
135-
if (mightBeDropped(sym)) retained += sym
136-
case _ =>
137-
}
138-
if (!noDirectRefsFrom(tree)) traverseChildren(tree)
139-
}
140-
def collect(stats: List[Tree]): Unit = stats foreach {
141-
case stat: ValDef if !stat.symbol.is(Lazy) =>
142-
traverse(stat)
143-
if (mightBeDropped(stat.symbol))
144-
(if (isWildcardStarArg(stat.rhs)) retained else seen) += stat.symbol
145-
case stat: DefTree =>
146-
inConstr = false
147-
traverse(stat)
148-
inConstr = true
149-
case stat =>
150-
traverse(stat)
151-
}
169+
def isRetained(acc: Symbol) = {
170+
!mightBeDropped(acc) || retainedPrivateVals(acc)
152171
}
153-
usage.collect(tree.body)
154-
155-
def isRetained(acc: Symbol) = !mightBeDropped(acc) || usage.retained(acc)
156172

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

@@ -170,6 +186,8 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
170186
}
171187
}
172188

189+
val dropped = mutable.Set[Symbol]()
190+
173191
// Split class body into statements that go into constructor and
174192
// definitions that are kept as members of the class.
175193
def splitStats(stats: List[Tree]): Unit = stats match {
@@ -183,6 +201,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
183201
clsStats += cpy.ValDef(stat)(rhs = EmptyTree)
184202
}
185203
else if (!stat.rhs.isEmpty) {
204+
dropped += sym
186205
sym.copySymDenotation(
187206
initFlags = sym.flags &~ Private,
188207
owner = constr.symbol).installAfter(thisTransform)
@@ -203,8 +222,10 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
203222

204223
// The initializers for the retained accessors */
205224
val copyParams = accessors flatMap { acc =>
206-
if (!isRetained(acc)) Nil
207-
else {
225+
if (!isRetained(acc)) {
226+
dropped += acc
227+
Nil
228+
} else {
208229
val target = if (acc.is(Method)) acc.field else acc
209230
if (!target.exists) Nil // this case arises when the parameter accessor is an alias
210231
else {
@@ -224,12 +245,13 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
224245
}
225246

226247
// Drop accessors that are not retained from class scope
227-
val dropped = usage.dropped
228248
if (dropped.nonEmpty) {
229249
val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error
230250
cls.copy(
231251
info = clsInfo.derivedClassInfo(
232252
decls = clsInfo.decls.filteredScope(!dropped.contains(_))))
253+
254+
// TODO: this happens to work only because Constructors is the last phase in group
233255
}
234256

235257
val (superCalls, followConstrStats) = constrStats.toList match {

src/dotty/tools/dotc/transform/Memoize.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,16 @@ import Decorators._
7070
lazy val field = sym.field.orElse(newField).asTerm
7171
if (sym.is(Accessor, butNot = NoFieldNeeded))
7272
if (sym.isGetter) {
73-
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
74-
if (isWildcardArg(rhs)) rhs = EmptyTree
75-
val fieldDef = transformFollowing(ValDef(field, rhs))
76-
val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field)))
77-
Thicket(fieldDef, getterDef)
78-
}
73+
var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
74+
if (isWildcardArg(rhs)) rhs = EmptyTree
75+
val fieldDef = transformFollowing(ValDef(field, rhs))
76+
val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info))
77+
Thicket(fieldDef, getterDef)
78+
}
7979
else if (sym.isSetter) {
8080
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
8181
val initializer = Assign(ref(field), ref(tree.vparamss.head.head.symbol))
82-
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer))
82+
cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
8383
}
8484
else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as
8585
// neither getters nor setters

0 commit comments

Comments
 (0)