Skip to content

Commit e2bd5d6

Browse files
committed
Erase arguments of phantom type
This commit removes arguments and parameter of phantom type from all applications. Arguments are evaluated before the application in case they have side effects. ``` def foo(i: Int, p: SomePhantom) = ??? foo(42, { println("hello"); getSomePhantom }) ``` becomes ``` def foo(i: Int) = ??? val x$0 = 42 val x$1 = { println("hello"); getSomePhantom } foo(x$0) ``` Note that now `def foo(i: Int)` and def `foo(i: Int, p: SomePhantom)` erase to the same signature. Tests for this commit where added in `Extra tests for Phantoms 1`, they where back ported as the semantics and runtime is not affected.
1 parent 975cee5 commit e2bd5d6

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package dotty.tools.dotc.transform
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.core.Contexts._
5+
import dotty.tools.dotc.core.Flags._
6+
import dotty.tools.dotc.core.NameKinds._
7+
import dotty.tools.dotc.core.Types._
8+
import dotty.tools.dotc.transform.TreeTransforms.{MiniPhaseTransform, TransformerInfo}
9+
10+
/** This phase extracts the arguments of phantom type before the application to avoid losing any
11+
* effects in the argument tree. This trivializes the removal of parameter in the Erasure phase.
12+
*
13+
* `f(x1,...)(y1,...)...(...)` with at least one phantom argument
14+
*
15+
* -->
16+
*
17+
* `val ev$f = f` // if `f` is some expression that needs evaluation
18+
* `val ev$x1 = x1`
19+
* ...
20+
* `val ev$y1 = y1`
21+
* ...
22+
* `ev$f(ev$x1,...)(ev$y1,...)...(...)`
23+
*
24+
*/
25+
class PhantomArgumentEval extends MiniPhaseTransform {
26+
import tpd._
27+
28+
override def phaseName: String = "phantomArgumentEval"
29+
30+
/** Check what the phase achieves, to be called at any point after it is finished. */
31+
override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match {
32+
case tree: Apply =>
33+
tree.args.foreach { arg =>
34+
assert(
35+
!arg.tpe.isPhantom ||
36+
(arg.isInstanceOf[Ident] && !arg.symbol.is(Method) && !arg.symbol.is(Lazy))
37+
)
38+
}
39+
case _ =>
40+
}
41+
42+
/* Tree transform */
43+
44+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = tree.tpe match {
45+
case _: MethodType => tree // Do the transformation higher in the tree if needed
46+
case _ =>
47+
def mkNewBlock(newApply: Tree, synthVals: List[ValDef]) = Block(synthVals, newApply)
48+
if (!hasPhantomArgs(tree)) tree
49+
else transformApplication(tree, mkNewBlock)
50+
}
51+
52+
override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = {
53+
if (!tree.rhs.tpe.isPhantom) super.transformAssign(tree)
54+
else {
55+
// Apply the same transformation to setters before their creation.
56+
val (synthVal, synthValRef) = mkSynthVal(tree.rhs)
57+
Block(List(synthVal), Assign(tree.lhs, synthValRef))
58+
}
59+
}
60+
61+
/* private methods */
62+
63+
/** Returns true if at least on of the arguments is a phantom.
64+
* Inner applies are also checked in case of multiple parameter list.
65+
*/
66+
private def hasPhantomArgs(tree: Apply)(implicit ctx: Context): Boolean = {
67+
tree.args.exists {
68+
case arg: Ident if !arg.symbol.is(Method) && !arg.symbol.is(Lazy) => false
69+
case arg => arg.tpe.isPhantom
70+
} || {
71+
tree.fun match {
72+
case fun: Apply => hasPhantomArgs(fun)
73+
case _ => false
74+
}
75+
}
76+
}
77+
78+
/** Collects all args (and possibly the function) as synthetic vals and replaces them in the tree by the reference to
79+
* the lifted its val.
80+
*/
81+
private def transformApplication(tree: Tree, mkTree: (Tree, List[ValDef]) => Tree)(implicit ctx: Context): Tree = tree match {
82+
case tree: Apply =>
83+
def mkNewApply(newFun: Tree, accSynthVals: List[ValDef]) = {
84+
val (synthVals, synthValRefs) = tree.args.map(mkSynthVal).unzip
85+
mkTree(cpy.Apply(tree)(newFun, synthValRefs), accSynthVals ::: synthVals)
86+
}
87+
transformApplication(tree.fun, mkNewApply)
88+
case tree: TypeApply =>
89+
def mkNewTypeApply(newFun: Tree, accSynthVals: List[ValDef]) =
90+
mkTree(cpy.TypeApply(tree)(newFun, tree.args), accSynthVals)
91+
transformApplication(tree.fun, mkNewTypeApply)
92+
case tree: Select if !tree.qualifier.isInstanceOf[New] =>
93+
val (accSynthVal, synthValRef) = mkSynthVal(tree.qualifier)
94+
mkTree(Select(synthValRef, tree.name), List(accSynthVal))
95+
case _ => mkTree(tree, Nil)
96+
}
97+
98+
/** Makes a synthetic val with the tree and creates a reference to it.
99+
* `tree` --> (`val ev$x = tree`, `ev$x`)
100+
*/
101+
private def mkSynthVal(tree: Tree)(implicit ctx: Context): (ValDef, Tree) = {
102+
val synthVal = SyntheticValDef(TempResultName.fresh(), tree)
103+
(synthVal, ref(synthVal.symbol))
104+
}
105+
}

0 commit comments

Comments
 (0)