1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
package dotty.tools.dotc
package transform
import core._
import DenotTransformers.SymTransformer
import Phases.Phase
import Contexts.Context
import Flags._
import Symbols._
import SymDenotations.SymDenotation
import ast.Trees._
import collection.mutable
import Decorators._
import NameOps._
import TreeTransforms.MiniPhaseTransform
import dotty.tools.dotc.transform.TreeTransforms.TransformerInfo
/** Remove companion objects that are empty
* Lots of constraints here:
* 1. It's impractical to place DropEmptyCompanions before lambda lift because dropped
* modules can be anywhere and have hard to trace references.
* 2. DropEmptyCompanions cannot be interleaved with LambdaLift or Flatten because
* they put things in liftedDefs sets which cause them to surface later. So
* removed modules resurface.
* 3. DropEmptyCompanions has to be before RestoreScopes.
* The solution to the constraints is to put DropEmptyCompanions between Flatten
* and RestoreScopes and to only start working once we are back on PackageDef
* level, so we know that all objects moved by LambdaLift and Flatten have arrived
* at their destination.
*/
class DropEmptyCompanions extends MiniPhaseTransform { thisTransform =>
import ast.tpd._
override def phaseName = "dropEmptyCompanions"
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Flatten])
override def transformPackageDef(pdef: PackageDef)(implicit ctx: Context, info: TransformerInfo) = {
/** Is `tree` an empty companion object? */
def isEmptyCompanion(tree: Tree) = tree match {
case TypeDef(_, impl: Template) if tree.symbol.is(SyntheticModule) &&
tree.symbol.companionClass.exists &&
impl.body.forall(_.symbol.isPrimaryConstructor) =>
ctx.log(i"removing ${tree.symbol}")
true
case _ =>
false
}
val dropped = pdef.stats.filter(isEmptyCompanion).map(_.symbol).toSet
/** Symbol is a $lzy field representing a module */
def isLazyModuleVar(sym: Symbol) =
sym.name.isLazyLocal &&
sym.owner.info.decl(sym.name.asTermName.nonLazyName).symbol.is(Module)
/** Symbol should be dropped together with a dropped companion object.
* Such symbols are:
* - lzy fields pointing to modules,
* - vals and getters representing modules.
*/
def symIsDropped(sym: Symbol): Boolean =
(sym.is(Module) || isLazyModuleVar(sym)) &&
dropped.contains(sym.info.resultType.typeSymbol)
/** Tree should be dropped because it (is associated with) an empty
* companion object. Such trees are
* - module classes of empty companion objects
* - definitions of lazy module variables or assignments to them.
* - vals and getters for empty companion objects
*/
def toDrop(stat: Tree): Boolean = stat match {
case stat: TypeDef => dropped.contains(stat.symbol)
case stat: ValOrDefDef => symIsDropped(stat.symbol)
case stat: Assign => symIsDropped(stat.lhs.symbol)
case _ => false
}
def prune(tree: Tree): Tree = tree match {
case tree @ TypeDef(name, impl @ Template(constr, _, _, _)) =>
cpy.TypeDef(tree)(
rhs = cpy.Template(impl)(
constr = cpy.DefDef(constr)(rhs = pruneLocals(constr.rhs)),
body = pruneStats(impl.body)))
case _ =>
tree
}
def pruneStats(stats: List[Tree]) =
stats.filterConserve(!toDrop(_)).mapConserve(prune)
def pruneLocals(expr: Tree) = expr match {
case Block(stats, expr) => cpy.Block(expr)(pruneStats(stats), expr)
case _ => expr
}
cpy.PackageDef(pdef)(pdef.pid, pruneStats(pdef.stats))
}
}
|