Skip to content

Commit ac8c0e2

Browse files
committed
Move private fields into constructor
Private fields that are accessed only from the constructor, and are accessed only after they are properly initialized are now moved into the constructor. This avoids creating a redundant objetc field. Good example: gcd in Rationals (see constrs.scala).
1 parent 6dfbcdd commit ac8c0e2

File tree

3 files changed

+193
-72
lines changed

3 files changed

+193
-72
lines changed

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

Lines changed: 107 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import collection.mutable
2121
/** This transform
2222
* - moves initializers from body to constructor.
2323
* - makes all supercalls explicit
24+
* - also moves private fields that are accessed only from constructor
25+
* into the constructor is possible.
2426
*/
2527
class Constructors extends MiniPhaseTransform with SymTransformer { thisTransform =>
2628
import tpd._
@@ -35,7 +37,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
3537
*/
3638
override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = {
3739
def ownerBecomesConstructor(owner: Symbol): Boolean =
38-
(owner.isLocalDummy || owner.isTerm && !owner.is(Method)) &&
40+
(owner.isLocalDummy || owner.isTerm && !owner.is(Method | Lazy)) &&
3941
owner.owner.isClass
4042
if (ownerBecomesConstructor(sym.owner))
4143
sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor)
@@ -48,38 +50,14 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
4850
private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) =
4951
tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall)
5052

51-
/** Adjustments performed when moving code into the constructor:
52-
* (1) Replace references to param accessors by constructor parameters
53-
* except possibly references to mutable variables, if `excluded = Mutable`.
54-
* (Mutable parameters should be replaced only during the super call)
55-
* (2) If the parameter accessor reference was to an alias getter,
56-
* drop the () when replacing by the parameter.
53+
/** Class members that can be eliminated if referenced only from their own
54+
* constructor.
5755
*/
58-
class IntoConstrMap(accessors: List[Symbol], params: List[Symbol]) extends TreeMap {
59-
private var excluded: FlagSet = _
60-
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
61-
case Ident(_) | Select(This(_), _) =>
62-
val sym = tree.symbol
63-
if (sym is (ParamAccessor, butNot = excluded)) {
64-
val param = sym.subst(accessors, params)
65-
if (param ne sym) ref(param).withPos(tree.pos)
66-
else tree
67-
}
68-
else tree
69-
case Apply(fn, Nil) =>
70-
val fn1 = transform(fn)
71-
if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor)
72-
fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass
73-
else cpy.Apply(tree)(fn1, Nil)
74-
case _ =>
75-
if (noDirectRefsFrom(tree)) tree else super.transform(tree)
76-
}
56+
private def mightBeDropped(sym: Symbol)(implicit ctx: Context) =
57+
sym.is(Private, butNot = KeeperFlags) && !sym.is(MutableParamAccessor)
7758

78-
def apply(tree: Tree, excluded: FlagSet)(implicit ctx: Context): Tree = {
79-
this.excluded = excluded
80-
transform(tree)
81-
}
82-
}
59+
private final val KeeperFlags = Method | Lazy | NotJavaPrivate
60+
private final val MutableParamAccessor = allOf(Mutable, ParamAccessor)
8361

8462
override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
8563
val cls = ctx.owner.asClass
@@ -102,7 +80,34 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
10280
}
10381
val paramSyms = vparamsWithOuter map (_.symbol)
10482

105-
val intoConstr = new IntoConstrMap(accessors, paramSyms)
83+
// Adjustments performed when moving code into the constructor:
84+
// (1) Replace references to param accessors by constructor parameters
85+
// except possibly references to mutable variables, if `excluded = Mutable`.
86+
// (Mutable parameters should be replaced only during the super call)
87+
// (2) If the parameter accessor reference was to an alias getter,
88+
// drop the () when replacing by the parameter.
89+
object intoConstr extends TreeMap {
90+
private var excluded: FlagSet = _
91+
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
92+
case Ident(_) | Select(This(_), _) =>
93+
var sym = tree.symbol
94+
if (sym is (ParamAccessor, butNot = excluded)) sym = sym.subst(accessors, paramSyms)
95+
if (sym.owner.isConstructor) ref(sym).withPos(tree.pos) else tree
96+
case Apply(fn, Nil) =>
97+
val fn1 = transform(fn)
98+
if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor)
99+
fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass
100+
else cpy.Apply(tree)(fn1, Nil)
101+
case _ =>
102+
if (noDirectRefsFrom(tree)) tree else super.transform(tree)
103+
}
104+
105+
def apply(tree: Tree, inSuperCall: Boolean = false)(implicit ctx: Context): Tree = {
106+
this.excluded = if (inSuperCall) EmptyFlags else Mutable
107+
transform(tree)
108+
}
109+
}
110+
106111
val superCalls = new mutable.ListBuffer[Tree]
107112

108113
// If parent is a constructor call, pull out the call into a separate
@@ -115,12 +120,12 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
115120
nme.CONSTRUCTOR),
116121
superArgs) =>
117122
val toClass = !superType.symbol.is(Trait)
118-
val mappedArgs = superArgs.map(
119-
intoConstr(_, excluded = if (toClass) Mutable else EmptyFlags))
123+
val mappedArgs = superArgs.map(intoConstr(_, inSuperCall = toClass))
120124
val receiver =
121125
if (toClass) Super(This(cls), tpnme.EMPTY, inConstrCall = true)
122126
else This(cls)
123-
superCalls += cpy.Apply(superApp)(
127+
superCalls +=
128+
cpy.Apply(superApp)(
124129
receiver.withPos(superNew.pos)
125130
.select(superSel.symbol).withPos(superSel.pos),
126131
mappedArgs)
@@ -129,67 +134,97 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
129134
}
130135
val parentTypeTrees = tree.parents.map(normalizeParent)
131136

137+
// Collect all private parameter accessors and value definitions that need
138+
// to be retained. There are several reasons why a parameter accessor or
139+
// definition might need to be retained:
140+
// 1. It is accessed after the constructor has finished
141+
// 2. It is accessed before it is defined
142+
// 3. It is accessed on an object other than `this`
143+
// 4. It is a mutable parameter accessor
144+
// 5. It is has a wildcard initializer `_`
145+
object usage extends TreeTraverser {
146+
private var inConstr: Boolean = true
147+
private val seen = mutable.Set[Symbol](accessors: _*)
148+
val retained = mutable.Set[Symbol]()
149+
def dropped: collection.Set[Symbol] = seen -- retained
150+
override def traverse(tree: Tree) = {
151+
val sym = tree.symbol
152+
tree match {
153+
case Ident(_) | Select(This(_), _) if inConstr && seen(tree.symbol) =>
154+
// could refer to definition in constructors, so no retention necessary
155+
case tree: RefTree =>
156+
if (mightBeDropped(sym)) retained += sym
157+
case _ =>
158+
}
159+
if (!noDirectRefsFrom(tree)) traverseChildren(tree)
160+
}
161+
def collect(stats: List[Tree]): Unit = stats foreach {
162+
case stat: ValDef if !stat.symbol.is(Lazy) =>
163+
traverse(stat)
164+
if (mightBeDropped(stat.symbol))
165+
(if (isWildcardStarArg(stat.rhs)) retained else seen) += stat.symbol
166+
case stat: DefTree =>
167+
inConstr = false
168+
traverse(stat)
169+
inConstr = true
170+
case stat =>
171+
traverse(stat)
172+
}
173+
}
174+
usage.collect(superCalls.toList ++ tree.body)
175+
176+
def isRetained(acc: Symbol) = !mightBeDropped(acc) || usage.retained(acc)
177+
178+
val constrStats, clsStats = new mutable.ListBuffer[Tree]
179+
132180
// Split class body into statements that go into constructor and
133181
// definitions that are kept as members of the class.
134-
def splitStats(stats: List[Tree]): (List[Tree], List[Tree]) = stats match {
182+
def splitStats(stats: List[Tree]): Unit = stats match {
135183
case stat :: stats1 =>
136-
val (constrStats, clsStats) = splitStats(stats1)
137184
stat match {
138-
case stat @ ValDef(mods, name, tpt, rhs) =>
139-
val inits =
140-
if (rhs.isEmpty || isWildcardArg(rhs)) Nil
141-
else Assign(ref(stat.symbol), intoConstr(rhs, excluded = Mutable))
142-
.withPos(stat.pos) :: Nil
143-
(inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats)
185+
case stat @ ValDef(mods, name, tpt, rhs) if !stat.symbol.is(Lazy) =>
186+
val sym = stat.symbol
187+
if (isRetained(sym)) {
188+
if (!rhs.isEmpty && !isWildcardArg(rhs))
189+
constrStats += Assign(ref(sym), intoConstr(rhs)).withPos(stat.pos)
190+
clsStats += cpy.ValDef(stat)(rhs = EmptyTree)
191+
}
192+
else if (!rhs.isEmpty) {
193+
sym.copySymDenotation(
194+
initFlags = sym.flags &~ Private,
195+
owner = constr.symbol).installAfter(thisTransform)
196+
constrStats += intoConstr(stat)
197+
}
144198
case _: DefTree =>
145-
(constrStats, stat :: clsStats)
199+
clsStats += stat
146200
case _ =>
147-
(intoConstr(stat, excluded = Mutable) :: constrStats, clsStats)
201+
constrStats += intoConstr(stat)
148202
}
203+
splitStats(stats1)
149204
case Nil =>
150205
(Nil, Nil)
151206
}
152-
val (constrStats, clsStats) = splitStats(tree.body)
153-
154-
// Collect all private parameter accessors that need to be retained
155-
// because they are accessed after the constructor has finished.
156-
val collectRetained = new TreeAccumulator[Set[Symbol]] {
157-
override def apply(retained: Set[Symbol], tree: Tree) = tree match {
158-
case tree: RefTree =>
159-
val sym = tree.symbol
160-
foldOver(
161-
if (sym.is(PrivateParamAccessor) && sym.owner == cls) retained + sym else retained,
162-
tree)
163-
case _ =>
164-
if (noDirectRefsFrom(tree)) retained else foldOver(retained, tree)
165-
}
166-
}
167-
val retainedPrivate = collectRetained(collectRetained(Set[Symbol](), constrStats), clsStats)
168-
def isRetained(acc: Symbol) =
169-
(!acc.is(Private) || acc.is(NotJavaPrivate) || retainedPrivate(acc))
207+
splitStats(tree.body)
170208

171209
val accessorFields = accessors.filterNot(_ is Method)
172-
val (retainedAccessors, droppedAccessors) = accessorFields.partition(isRetained)
173210

174211
// The initializers for the retained accessors */
175-
val copyParams = retainedAccessors.map(acc =>
212+
val copyParams = accessorFields.filter(isRetained).map(acc =>
176213
Assign(ref(acc), ref(acc.subst(accessors, paramSyms))).withPos(tree.pos))
177214

178215
// Drop accessors that are not retained from class scope
179-
if (droppedAccessors.nonEmpty) {
216+
val dropped = usage.dropped
217+
if (dropped.nonEmpty) {
180218
val clsInfo = cls.classInfo // TODO investigate: expand clsInfo to cls.info => dotty type error
181219
cls.copy(
182220
info = clsInfo.derivedClassInfo(
183-
decls = clsInfo.decls.filteredScope(!droppedAccessors.contains(_))))
221+
decls = clsInfo.decls.filteredScope(!dropped.contains(_))))
184222
}
185223

186224
cpy.Template(tree)(
187225
constr = cpy.DefDef(constr)(
188-
rhs = Block(superCalls.toList ::: copyParams ::: constrStats, unitLiteral)),
226+
rhs = Block(superCalls.toList ::: copyParams ::: constrStats.toList, unitLiteral)),
189227
parents = parentTypeTrees,
190-
body = clsStats filter {
191-
case vdef: ValDef => !droppedAccessors.contains(vdef.symbol)
192-
case _ => true
193-
})
228+
body = clsStats.toList)
194229
}
195230
}

tests/pos/Fileish.scala

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Inspired by the original Fileish,
2+
// testing combinations of lazy and non-lazy vals for their treatment in constructors
3+
package dotty.tools
4+
package io
5+
6+
import java.io.{ InputStream }
7+
import java.util.jar.JarEntry
8+
import language.postfixOps
9+
10+
/** A common interface for File-based things and Stream-based things.
11+
* (In particular, io.File and JarEntry.)
12+
*/
13+
class Fileish(val path: Path, val input: () => InputStream) extends Streamable.Chars {
14+
def inputStream() = input()
15+
16+
def parent = path.parent
17+
def name = path.name
18+
def isSourceFile = path.hasExtension("java", "scala")
19+
20+
private lazy val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
21+
lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
22+
lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
23+
24+
override def toString = path.path
25+
}
26+
class Fileish2(val path: Path, val input: () => InputStream) extends Streamable.Chars {
27+
def inputStream() = input()
28+
29+
def parent = path.parent
30+
def name = path.name
31+
def isSourceFile = path.hasExtension("java", "scala")
32+
33+
private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
34+
lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
35+
lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
36+
37+
override def toString = path.path
38+
}
39+
40+
class Fileish3(val path: Path, val input: () => InputStream) extends Streamable.Chars {
41+
def inputStream() = input()
42+
43+
def parent = path.parent
44+
def name = path.name
45+
def isSourceFile = path.hasExtension("java", "scala")
46+
47+
private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
48+
private val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
49+
private val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
50+
51+
override def toString = path.path
52+
}
53+

tests/pos/constrs.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class Foo(x: Int, var y: Int) {
2+
3+
val z: Int = 0
4+
5+
var u: Int = _
6+
7+
def f = x
8+
9+
}
10+
11+
class Baz(val base: Int) {
12+
13+
}
14+
15+
16+
class Bar(base: Int, byName: => String, local: Int) extends Baz(base + local) {
17+
18+
def f() = println(base.toString + byName)
19+
20+
}
21+
22+
class Rational(n: Int, d: Int) {
23+
def gcd(x: Int, y: Int): Int = ???
24+
private val x = gcd(n, d)
25+
def numer = n / x
26+
def denom = d / x
27+
}
28+
class Rational2(n: Int, d: Int) {
29+
def gcd(x: Int, y: Int): Int = ???
30+
private val x = gcd(n, d)
31+
val numer = n / x
32+
val denom = d / x
33+
}

0 commit comments

Comments
 (0)