|
| 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