Skip to content

Commit c637535

Browse files
committed
Fix #2869: Have constructors run after group of Memoize
As explained in the code comment, the two get too entangled otherwise which leads to parameters being stored as class fields when they should not.
1 parent 5e05d3f commit c637535

File tree

4 files changed

+34
-11
lines changed

4 files changed

+34
-11
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ class Compiler {
8787
new LazyVals, // Expand lazy vals
8888
new Memoize, // Add private fields to getters and setters
8989
new NonLocalReturns, // Expand non-local returns
90-
new CapturedVars, // Represent vars captured by closures as heap objects
91-
new Constructors, // Collect initialization code in primary constructors
90+
new CapturedVars), // Represent vars captured by closures as heap objects
91+
List(new Constructors, // Collect initialization code in primary constructors
9292
// Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it
9393
new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions.
9494
new GetClass, // Rewrites getClass calls on primitive types.

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,24 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
3030
import tpd._
3131

3232
override def phaseName: String = "constructors"
33-
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize], classOf[HoistSuperArgs])
33+
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[HoistSuperArgs])
34+
override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set(classOf[Memoize])
35+
// Memoized needs to be finished because we depend on the ownerchain after Memoize
36+
// when checking whether an ident is an access in a constructor or outside it.
37+
// This test is done in the right-hand side of a value definition. If Memoize
38+
// was in the same group as Constructors, the test on the rhs ident would be
39+
// performed before the rhs undergoes the owner change. This would lead
40+
// to more symbls being retained as parameters. Test case is
41+
//
42+
// dotc tests/pos/captuing.scala -Xprint:constr
43+
//
44+
// `sf` should not be retained as a filed of class `MT`.
45+
46+
/** The private vals that are known to be retained as class fields */
47+
private val retainedPrivateVals = mutable.Set[Symbol]()
48+
49+
/** The private vals whose definition comes before the current focus */
50+
private val seenPrivateVals = mutable.Set[Symbol]()
3451

3552
// Collect all private parameter accessors and value definitions that need
3653
// to be retained. There are several reasons why a parameter accessor or
@@ -40,14 +57,10 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
4057
// 3. It is accessed on an object other than `this`
4158
// 4. It is a mutable parameter accessor
4259
// 5. It is has a wildcard initializer `_`
43-
private val retainedPrivateVals = mutable.Set[Symbol]()
44-
private val seenPrivateVals = mutable.Set[Symbol]()
45-
4660
private def markUsedPrivateSymbols(tree: RefTree)(implicit ctx: Context): Unit = {
4761

4862
val sym = tree.symbol
49-
def retain() =
50-
retainedPrivateVals.add(sym)
63+
def retain() = retainedPrivateVals.add(sym)
5164

5265
if (sym.exists && sym.owner.isClass && mightBeDropped(sym)) {
5366
val owner = sym.owner.asClass
@@ -58,7 +71,8 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
5871
val method = ctx.owner.enclosingMethod
5972
method.isPrimaryConstructor && ctx.owner.enclosingClass == owner
6073
}
61-
if (inConstructor && (sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
74+
if (inConstructor &&
75+
(sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
6276
// used inside constructor, accessed on this,
6377
// could use constructor argument instead, no need to retain field
6478
}
@@ -79,8 +93,7 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
7993
}
8094

8195
override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
82-
if (mightBeDropped(tree.symbol))
83-
(if (isWildcardStarArg(tree.rhs)) retainedPrivateVals else seenPrivateVals) += tree.symbol
96+
if (mightBeDropped(tree.symbol)) seenPrivateVals += tree.symbol
8497
tree
8598
}
8699

tests/run/capturing.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
private final java.lang.String MT.s

tests/run/capturing.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class MT(sf: MT => String) {
2+
// `s` is retained as a field, but `sf` should not be.
3+
val s = sf(this)
4+
}
5+
object Test extends App {
6+
def printFields(obj: Any) =
7+
println(obj.getClass.getDeclaredFields.map(_.toString).sorted.deep.mkString("\n"))
8+
printFields(new MT(_ => ""))
9+
}

0 commit comments

Comments
 (0)