aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dotty/tools/dotc/transform/Constructors.scala179
-rw-r--r--tests/pos/Fileish.scala53
-rw-r--r--tests/pos/constrs.scala33
3 files changed, 193 insertions, 72 deletions
diff --git a/src/dotty/tools/dotc/transform/Constructors.scala b/src/dotty/tools/dotc/transform/Constructors.scala
index 3d2f5a0d7..9cb348dff 100644
--- a/src/dotty/tools/dotc/transform/Constructors.scala
+++ b/src/dotty/tools/dotc/transform/Constructors.scala
@@ -21,6 +21,8 @@ import collection.mutable
/** This transform
* - moves initializers from body to constructor.
* - makes all supercalls explicit
+ * - also moves private fields that are accessed only from constructor
+ * into the constructor if possible.
*/
class Constructors extends MiniPhaseTransform with SymTransformer { thisTransform =>
import tpd._
@@ -35,7 +37,7 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
*/
override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = {
def ownerBecomesConstructor(owner: Symbol): Boolean =
- (owner.isLocalDummy || owner.isTerm && !owner.is(Method)) &&
+ (owner.isLocalDummy || owner.isTerm && !owner.is(Method | Lazy)) &&
owner.owner.isClass
if (ownerBecomesConstructor(sym.owner))
sym.copySymDenotation(owner = sym.owner.enclosingClass.primaryConstructor)
@@ -48,38 +50,14 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) =
tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall)
- /** Adjustments performed when moving code into the constructor:
- * (1) Replace references to param accessors by constructor parameters
- * except possibly references to mutable variables, if `excluded = Mutable`.
- * (Mutable parameters should be replaced only during the super call)
- * (2) If the parameter accessor reference was to an alias getter,
- * drop the () when replacing by the parameter.
+ /** Class members that can be eliminated if referenced only from their own
+ * constructor.
*/
- class IntoConstrMap(accessors: List[Symbol], params: List[Symbol]) extends TreeMap {
- private var excluded: FlagSet = _
- override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
- case Ident(_) | Select(This(_), _) =>
- val sym = tree.symbol
- if (sym is (ParamAccessor, butNot = excluded)) {
- val param = sym.subst(accessors, params)
- if (param ne sym) ref(param).withPos(tree.pos)
- else tree
- }
- else tree
- case Apply(fn, Nil) =>
- val fn1 = transform(fn)
- if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor)
- fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass
- else cpy.Apply(tree)(fn1, Nil)
- case _ =>
- if (noDirectRefsFrom(tree)) tree else super.transform(tree)
- }
+ private def mightBeDropped(sym: Symbol)(implicit ctx: Context) =
+ sym.is(Private, butNot = KeeperFlags) && !sym.is(MutableParamAccessor)
- def apply(tree: Tree, excluded: FlagSet)(implicit ctx: Context): Tree = {
- this.excluded = excluded
- transform(tree)
- }
- }
+ private final val KeeperFlags = Method | Lazy | NotJavaPrivate
+ private final val MutableParamAccessor = allOf(Mutable, ParamAccessor)
override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
val cls = ctx.owner.asClass
@@ -102,7 +80,34 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
}
val paramSyms = vparamsWithOuter map (_.symbol)
- val intoConstr = new IntoConstrMap(accessors, paramSyms)
+ // Adjustments performed when moving code into the constructor:
+ // (1) Replace references to param accessors by constructor parameters
+ // except possibly references to mutable variables, if `excluded = Mutable`.
+ // (Mutable parameters should be replaced only during the super call)
+ // (2) If the parameter accessor reference was to an alias getter,
+ // drop the () when replacing by the parameter.
+ object intoConstr extends TreeMap {
+ private var excluded: FlagSet = _
+ override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
+ case Ident(_) | Select(This(_), _) =>
+ var sym = tree.symbol
+ if (sym is (ParamAccessor, butNot = excluded)) sym = sym.subst(accessors, paramSyms)
+ if (sym.owner.isConstructor) ref(sym).withPos(tree.pos) else tree
+ case Apply(fn, Nil) =>
+ val fn1 = transform(fn)
+ if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor)
+ fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass
+ else cpy.Apply(tree)(fn1, Nil)
+ case _ =>
+ if (noDirectRefsFrom(tree)) tree else super.transform(tree)
+ }
+
+ def apply(tree: Tree, inSuperCall: Boolean = false)(implicit ctx: Context): Tree = {
+ this.excluded = if (inSuperCall) EmptyFlags else Mutable
+ transform(tree)
+ }
+ }
+
val superCalls = new mutable.ListBuffer[Tree]
// If parent is a constructor call, pull out the call into a separate
@@ -115,12 +120,12 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
nme.CONSTRUCTOR),
superArgs) =>
val toClass = !superType.symbol.is(Trait)
- val mappedArgs = superArgs.map(
- intoConstr(_, excluded = if (toClass) Mutable else EmptyFlags))
+ val mappedArgs = superArgs.map(intoConstr(_, inSuperCall = toClass))
val receiver =
if (toClass) Super(This(cls), tpnme.EMPTY, inConstrCall = true)
else This(cls)
- superCalls += cpy.Apply(superApp)(
+ superCalls +=
+ cpy.Apply(superApp)(
receiver.withPos(superNew.pos)
.select(superSel.symbol).withPos(superSel.pos),
mappedArgs)
@@ -129,67 +134,97 @@ class Constructors extends MiniPhaseTransform with SymTransformer { thisTransfor
}
val parentTypeTrees = tree.parents.map(normalizeParent)
+ // 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) = {
+ 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)
+ }
+ }
+ usage.collect(superCalls.toList ++ tree.body)
+
+ def isRetained(acc: Symbol) = !mightBeDropped(acc) || usage.retained(acc)
+
+ val constrStats, clsStats = new mutable.ListBuffer[Tree]
+
// Split class body into statements that go into constructor and
// definitions that are kept as members of the class.
- def splitStats(stats: List[Tree]): (List[Tree], List[Tree]) = stats match {
+ def splitStats(stats: List[Tree]): Unit = stats match {
case stat :: stats1 =>
- val (constrStats, clsStats) = splitStats(stats1)
stat match {
- case stat @ ValDef(mods, name, tpt, rhs) =>
- val inits =
- if (rhs.isEmpty || isWildcardArg(rhs)) Nil
- else Assign(ref(stat.symbol), intoConstr(rhs, excluded = Mutable))
- .withPos(stat.pos) :: Nil
- (inits ::: constrStats, cpy.ValDef(stat)(rhs = EmptyTree) :: clsStats)
+ case stat @ ValDef(mods, name, tpt, rhs) if !stat.symbol.is(Lazy) =>
+ val sym = stat.symbol
+ if (isRetained(sym)) {
+ if (!rhs.isEmpty && !isWildcardArg(rhs))
+ constrStats += Assign(ref(sym), intoConstr(rhs)).withPos(stat.pos)
+ clsStats += cpy.ValDef(stat)(rhs = EmptyTree)
+ }
+ else if (!rhs.isEmpty) {
+ sym.copySymDenotation(
+ initFlags = sym.flags &~ Private,
+ owner = constr.symbol).installAfter(thisTransform)
+ constrStats += intoConstr(stat)
+ }
case _: DefTree =>
- (constrStats, stat :: clsStats)
+ clsStats += stat
case _ =>
- (intoConstr(stat, excluded = Mutable) :: constrStats, clsStats)
+ constrStats += intoConstr(stat)
}
+ splitStats(stats1)
case Nil =>
(Nil, Nil)
}
- val (constrStats, clsStats) = splitStats(tree.body)
-
- // Collect all private parameter accessors that need to be retained
- // because they are accessed after the constructor has finished.
- val collectRetained = new TreeAccumulator[Set[Symbol]] {
- override def apply(retained: Set[Symbol], tree: Tree) = tree match {
- case tree: RefTree =>
- val sym = tree.symbol
- foldOver(
- if (sym.is(PrivateParamAccessor) && sym.owner == cls) retained + sym else retained,
- tree)
- case _ =>
- if (noDirectRefsFrom(tree)) retained else foldOver(retained, tree)
- }
- }
- val retainedPrivate = collectRetained(collectRetained(Set[Symbol](), constrStats), clsStats)
- def isRetained(acc: Symbol) =
- (!acc.is(Private) || acc.is(NotJavaPrivate) || retainedPrivate(acc))
+ splitStats(tree.body)
val accessorFields = accessors.filterNot(_ is Method)
- val (retainedAccessors, droppedAccessors) = accessorFields.partition(isRetained)
// The initializers for the retained accessors */
- val copyParams = retainedAccessors.map(acc =>
+ val copyParams = accessorFields.filter(isRetained).map(acc =>
Assign(ref(acc), ref(acc.subst(accessors, paramSyms))).withPos(tree.pos))
// Drop accessors that are not retained from class scope
- if (droppedAccessors.nonEmpty) {
+ 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(!droppedAccessors.contains(_))))
+ decls = clsInfo.decls.filteredScope(!dropped.contains(_))))
}
cpy.Template(tree)(
constr = cpy.DefDef(constr)(
- rhs = Block(superCalls.toList ::: copyParams ::: constrStats, unitLiteral)),
+ rhs = Block(superCalls.toList ::: copyParams ::: constrStats.toList, unitLiteral)),
parents = parentTypeTrees,
- body = clsStats filter {
- case vdef: ValDef => !droppedAccessors.contains(vdef.symbol)
- case _ => true
- })
+ body = clsStats.toList)
}
} \ No newline at end of file
diff --git a/tests/pos/Fileish.scala b/tests/pos/Fileish.scala
new file mode 100644
index 000000000..d226bb0bd
--- /dev/null
+++ b/tests/pos/Fileish.scala
@@ -0,0 +1,53 @@
+// Inspired by the original Fileish,
+// testing combinations of lazy and non-lazy vals for their treatment in constructors
+package dotty.tools
+package io
+
+import java.io.{ InputStream }
+import java.util.jar.JarEntry
+import language.postfixOps
+
+/** A common interface for File-based things and Stream-based things.
+ * (In particular, io.File and JarEntry.)
+ */
+class Fileish(val path: Path, val input: () => InputStream) extends Streamable.Chars {
+ def inputStream() = input()
+
+ def parent = path.parent
+ def name = path.name
+ def isSourceFile = path.hasExtension("java", "scala")
+
+ private lazy val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
+ lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
+ lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
+
+ override def toString = path.path
+}
+class Fileish2(val path: Path, val input: () => InputStream) extends Streamable.Chars {
+ def inputStream() = input()
+
+ def parent = path.parent
+ def name = path.name
+ def isSourceFile = path.hasExtension("java", "scala")
+
+ private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
+ lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
+ lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
+
+ override def toString = path.path
+}
+
+class Fileish3(val path: Path, val input: () => InputStream) extends Streamable.Chars {
+ def inputStream() = input()
+
+ def parent = path.parent
+ def name = path.name
+ def isSourceFile = path.hasExtension("java", "scala")
+
+ private val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
+ private val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
+ private val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
+
+ override def toString = path.path
+}
+
diff --git a/tests/pos/constrs.scala b/tests/pos/constrs.scala
new file mode 100644
index 000000000..dc0e1a369
--- /dev/null
+++ b/tests/pos/constrs.scala
@@ -0,0 +1,33 @@
+class Foo(x: Int, var y: Int) {
+
+ val z: Int = 0
+
+ var u: Int = _
+
+ def f = x
+
+}
+
+class Baz(val base: Int) {
+
+}
+
+
+class Bar(base: Int, byName: => String, local: Int) extends Baz(base + local) {
+
+ def f() = println(base.toString + byName)
+
+}
+
+class Rational(n: Int, d: Int) {
+ def gcd(x: Int, y: Int): Int = ???
+ private val x = gcd(n, d)
+ def numer = n / x
+ def denom = d / x
+}
+class Rational2(n: Int, d: Int) {
+ def gcd(x: Int, y: Int): Int = ???
+ private val x = gcd(n, d)
+ val numer = n / x
+ val denom = d / x
+}