|
| 1 | +package dotty.tools.dotc |
| 2 | +package transform |
| 3 | + |
| 4 | +import core._ |
| 5 | +import Decorators._ |
| 6 | +import Types._ |
| 7 | +import Contexts._ |
| 8 | +import Symbols._ |
| 9 | +import Constants._ |
| 10 | +import Flags._ |
| 11 | +import ast.Trees._ |
| 12 | +import ast.{TreeTypeMap, tpd} |
| 13 | +import tasty.TreePickler.Hole |
| 14 | +import SymUtils._ |
| 15 | +import NameKinds._ |
| 16 | + |
| 17 | +import scala.collection.mutable |
| 18 | +import dotty.tools.dotc.core.StdNames._ |
| 19 | + |
| 20 | + |
| 21 | +/** Translates macro implementation. |
| 22 | + * Checks that the phase consistency principle (PCP) holds on the body of a macro. |
| 23 | + * |
| 24 | + * |
| 25 | + * Transforms an inline macro definitions we assume that we have a single ~ directly as the RHS. |
| 26 | + * We will transform the definition from |
| 27 | + * ``` |
| 28 | + * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... x1 ... '{ ... T1 ... x1 ... '(y) ... } } |
| 29 | + * ``` |
| 30 | + * to |
| 31 | + * ``` |
| 32 | + * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~foo$splice$1[T1, ...]('[T1], ..., x1, ..., '(y1), ...) |
| 33 | + * |
| 34 | + * def foo$splice$1[T1, ...](T1$1: Type[T1], ..., x1$1: X, ..., y1: Expr[Y], ....): Z = |
| 35 | + * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } |
| 36 | + * ``` |
| 37 | + * Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are |
| 38 | + * passed as their actual runtime value. See `isStage0Value`. Other `inline` arguments such as functions are handled |
| 39 | + * like `y1: Y`. |
| 40 | + * |
| 41 | + * |
| 42 | + * At inline site we will call reflectively the static method `foo$splice$1` with the interpreted parameters. |
| 43 | + */ |
| 44 | +class MacrosSplitter extends ReifyQuotes { |
| 45 | + import ast.tpd._ |
| 46 | + |
| 47 | + override def phaseName: String = "macrosSplitter" |
| 48 | + |
| 49 | + override def run(implicit ctx: Context): Unit = |
| 50 | + if (ctx.compilationUnit.containsQuotesOrSplices) super.run |
| 51 | + |
| 52 | + protected override def newTransformer(implicit ctx: Context): Transformer = { |
| 53 | + new Transformer { |
| 54 | + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { |
| 55 | + case tree: PackageDef => super.transform(tree) |
| 56 | + case tree: TypeDef => super.transform(tree) |
| 57 | + case tree: Template => super.transform(tree) |
| 58 | + case macroDefTree: DefDef if macroDefTree.symbol.is(Inline) => |
| 59 | + |
| 60 | + val reifier = new Reifier(true, null, 1, new LevelInfo, new mutable.ListBuffer[Tree]) |
| 61 | + |
| 62 | + val transformedTree = reifier.transform(macroDefTree) // Ignore output, we only need the its embedding |
| 63 | + |
| 64 | + if (reifier.embedded.isEmpty) macroDefTree // Not a macro |
| 65 | + else { |
| 66 | + if (!macroDefTree.symbol.isStatic) // TODO remove restriction (issue #4803) |
| 67 | + ctx.error("Inline macro method must be a static method.", macroDefTree.pos) |
| 68 | + if (InlineSplice.unapply(macroDefTree.rhs).isEmpty) { // TODO allow multiple splices (issue #4801) |
| 69 | + ctx.error( |
| 70 | + """Malformed inline macro. |
| 71 | + | |
| 72 | + |Expected the ~ to be at the top of the RHS: |
| 73 | + | inline def foo(...): Int = ~impl(...) |
| 74 | + |or |
| 75 | + | inline def foo(...): Int = ~{ |
| 76 | + | val x = 1 |
| 77 | + | impl(... x ...) |
| 78 | + | } |
| 79 | + """.stripMargin, macroDefTree.rhs.pos) |
| 80 | + EmptyTree |
| 81 | + } |
| 82 | + else { |
| 83 | + val splicers = List.newBuilder[DefDef] |
| 84 | + val transformer = new TreeMap() { |
| 85 | + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { |
| 86 | + case Hole(idx, args) => |
| 87 | + val targs = args.filter(_.isType).map(_.tpe) |
| 88 | + |
| 89 | + val Block((lambdaDef: tpd.DefDef) :: Nil, _) = reifier.embedded(idx) |
| 90 | + |
| 91 | + val splicer = genSplicer(macroDefTree, lambdaDef, targs) |
| 92 | + splicers += splicer |
| 93 | + |
| 94 | + val liftedArgs = args.map { arg => |
| 95 | + if (arg.symbol.is(Inline) || arg.symbol == defn.TastyTopLevelSplice_tastyContext) arg |
| 96 | + else if (arg.isType) ref(defn.QuotedType_apply).appliedToType(arg.tpe) |
| 97 | + else ref(defn.QuotedExpr_apply).appliedToType(arg.tpe.widen).appliedTo(arg) |
| 98 | + } |
| 99 | + ref(splicer.symbol).appliedToTypes(targs).appliedToArgs(liftedArgs).select(nme.UNARY_~) |
| 100 | + case _ => super.transform(tree) |
| 101 | + } |
| 102 | + } |
| 103 | + val newRhs = transformer.transform(transformedTree.asInstanceOf[DefDef].rhs) |
| 104 | + val newDef = cpy.DefDef(macroDefTree)(rhs = newRhs) |
| 105 | + Thicket(newDef :: splicers.result()) |
| 106 | + } |
| 107 | + } |
| 108 | + case _ => |
| 109 | + tree |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + /** Generates splicer method from a lambda generated by `ReifyQuotes` |
| 115 | + * |
| 116 | + * Transforms `lambdaDef` (with the given `targs`) |
| 117 | + * ``` |
| 118 | + * (args: Seq[Any]) => { |
| 119 | + * val T1$1 = args(0).asInstanceOf[Type[T1]] |
| 120 | + * ... |
| 121 | + * val x1$1 = args(0).asInstanceOf[X] |
| 122 | + * ... |
| 123 | + * val y1$1 = args(1).asInstanceOf[Expr[Y]] |
| 124 | + * ... |
| 125 | + * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } |
| 126 | + * } |
| 127 | + * ``` |
| 128 | + * |
| 129 | + * into |
| 130 | + * |
| 131 | + * ``` |
| 132 | + * def foo$splice$1[T1, ...](T1$1: Type[T1], ..., x1$1: X, ..., y1: Expr[Y], ....): Z = |
| 133 | + * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } |
| 134 | + * ``` |
| 135 | + */ |
| 136 | + private def genSplicer(macroDefTree: DefDef, lambdaDef: DefDef, targs: List[Type])(implicit ctx: Context): DefDef = { |
| 137 | + val sym = macroDefTree.symbol |
| 138 | + |
| 139 | + val DefDef(_, _, _, _, Block(args: List[ValDef] @unchecked, body: Tree @unchecked)) = lambdaDef |
| 140 | + |
| 141 | + val splicerRetType = { |
| 142 | + val methodType = MethodType(args.map(_.name), args.map(_.tpt.tpe), lambdaDef.symbol.info.finalResultType) |
| 143 | + if (targs.isEmpty) methodType |
| 144 | + else { |
| 145 | + val tpArgSyms = targs.map(_.typeSymbol) |
| 146 | + val tpArgName = tpArgSyms.map(_.name.asTypeName) |
| 147 | + val tpArgBounds = tpArgSyms.map(_.info.bounds) |
| 148 | + PolyType(tpArgName)(_ => tpArgBounds, pt => methodType.subst(targs.map(_.typeSymbol), pt.paramRefs)) |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + val splicerName = UniqueSpliceName.fresh(sym.name.asTermName) |
| 153 | + val splicerSym = ctx.newSymbol(sym.owner, splicerName, sym.flags &~ Inline &~ Implicit, splicerRetType, sym.privateWithin, sym.coord).asTerm |
| 154 | + |
| 155 | + def treeTypeMap(tparams: List[Type], params: List[Tree]) = { |
| 156 | + val treeMap: Tree => Tree = { |
| 157 | + val map = args.map(_.symbol).zip(params).toMap |
| 158 | + tree => map.getOrElse(tree.symbol, tree) |
| 159 | + } |
| 160 | + val typeMap: Type => Type = new TypeMap() { |
| 161 | + private val map = macroDefTree.tparams.map(_.symbol).zip(tparams).toMap |
| 162 | + def apply(tp: Type): Type = map.getOrElse(tp.typeSymbol, mapOver(tp)) |
| 163 | + } |
| 164 | + new TreeTypeMap( |
| 165 | + typeMap = typeMap, treeMap = treeMap, |
| 166 | + oldOwners = lambdaDef.symbol :: Nil, newOwners = splicerSym :: Nil, |
| 167 | + substFrom = lambdaDef.tparams.map(_.symbol), substTo = tparams.map(_.typeSymbol) |
| 168 | + ) |
| 169 | + } |
| 170 | + |
| 171 | + polyDefDef(splicerSym, tparams => vparamss => treeTypeMap(tparams, vparamss.head).transform(body)) |
| 172 | + } |
| 173 | + |
| 174 | + /** InlineSplice is used to detect cases where the expansion |
| 175 | + * consists of a (possibly multiple & nested) block or a sole expression. |
| 176 | + */ |
| 177 | + object InlineSplice { |
| 178 | + def unapply(tree: Tree)(implicit ctx: Context): Option[Select] = { |
| 179 | + tree match { |
| 180 | + case expansion: Select if expansion.symbol.isSplice => Some(expansion) |
| 181 | + case Block(List(stat), Literal(Constant(()))) => unapply(stat) |
| 182 | + case Block(Nil, expr) => unapply(expr) |
| 183 | + case _ => None |
| 184 | + } |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | +} |
0 commit comments