Skip to content

Fix #2869: Have constructors run after group of Memoize #2870

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 4 commits into from
Jul 15, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ class Compiler {
new LazyVals, // Expand lazy vals
new Memoize, // Add private fields to getters and setters
new NonLocalReturns, // Expand non-local returns
new CapturedVars, // Represent vars captured by closures as heap objects
new Constructors, // Collect initialization code in primary constructors
new CapturedVars), // Represent vars captured by closures as heap objects
List(new Constructors, // Collect initialization code in primary constructors
// Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it
new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions.
new GetClass, // Rewrites getClass calls on primitive types.
Expand Down
27 changes: 18 additions & 9 deletions compiler/src/dotty/tools/dotc/transform/Constructors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,20 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
import tpd._

override def phaseName: String = "constructors"
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize], classOf[HoistSuperArgs])
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[HoistSuperArgs])
override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set(classOf[Memoize])
// Memoized needs to be finished because we depend on the ownerchain after Memoize
// when checking whether an ident is an access in a constructor or outside it.
// This test is done in the right-hand side of a value definition. If Memoize
// was in the same group as Constructors, the test on the rhs ident would be
// performed before the rhs undergoes the owner change. This would lead
// to more symbols being retained as parameters. Test case in run/capturing.scala.

/** The private vals that are known to be retained as class fields */
private val retainedPrivateVals = mutable.Set[Symbol]()

/** The private vals whose definition comes before the current focus */
private val seenPrivateVals = mutable.Set[Symbol]()

// Collect all private parameter accessors and value definitions that need
// to be retained. There are several reasons why a parameter accessor or
Expand All @@ -40,14 +53,10 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
// 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)
def retain() = retainedPrivateVals.add(sym)

if (sym.exists && sym.owner.isClass && mightBeDropped(sym)) {
val owner = sym.owner.asClass
Expand All @@ -58,7 +67,8 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
val method = ctx.owner.enclosingMethod
method.isPrimaryConstructor && ctx.owner.enclosingClass == owner
}
if (inConstructor && (sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
if (inConstructor &&
(sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
// used inside constructor, accessed on this,
// could use constructor argument instead, no need to retain field
}
Expand All @@ -79,8 +89,7 @@ class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { th
}

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
Member

Choose a reason for hiding this comment

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

Why was this condition dropped?

Copy link
Contributor Author

@odersky odersky Jul 14, 2017

Choose a reason for hiding this comment

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

It is (probably) dead code. The test looks non-sensical to me. I fail to see how a definition of a class member can end up as something like this:

 val xs = y: _*

Even if it did, why would it make a difference? I put in an assert to flag the condition, but it never triggered in all our tests.

if (mightBeDropped(tree.symbol)) seenPrivateVals += tree.symbol
tree
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/LambdaLift.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform

override def relaxedTyping = true

override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Constructors], classOf[HoistSuperArgs])
override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set(classOf[Constructors], classOf[HoistSuperArgs])
// Constructors has to happen before LambdaLift because the lambda lift logic
// becomes simpler if it can assume that parameter accessors have already been
// converted to parameters in super calls. Without this it is very hard to get
Expand Down
1 change: 1 addition & 0 deletions tests/run/capturing.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
private final java.lang.String MT.s
9 changes: 9 additions & 0 deletions tests/run/capturing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class MT(sf: MT => String) {
// `s` is retained as a field, but `sf` should not be.
val s = sf(this)
}
object Test extends App {
Copy link
Member

@smarter smarter Jul 14, 2017

Choose a reason for hiding this comment

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

I think we've deprecated/discouraged the use of App

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes but I am not sure why? I hate to write all these main methods.

Copy link
Member

Choose a reason for hiding this comment

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

See #559

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am still not sure why we use LegacyApp instead of App now. They seem to be equivalent.

Copy link
Member

Choose a reason for hiding this comment

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

/cc @DarkDimius do you remember why LegacyApp had to be introduced?

def printFields(obj: Any) =
println(obj.getClass.getDeclaredFields.map(_.toString).sorted.deep.mkString("\n"))
printFields(new MT(_ => ""))
}
1 change: 0 additions & 1 deletion tests/run/variable-pattern-access.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Fields of A:
private final int A.a
private final int A.b
private final scala.Tuple2 A.$1$
# Methods of A:
public int A.a()
public int A.b()