From 27dc0c5a5ef6bbfdd23a2ae47054c0d0134adf98 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 11 Jan 2019 15:05:13 +0100 Subject: [PATCH 01/14] Extract quote reification from Staging --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../dotty/tools/dotc/quoted/TreeCleaner.scala | 2 +- .../tools/dotc/transform/ReifyQuotes.scala | 414 ++++++++++++++++++ .../dotty/tools/dotc/transform/Staging.scala | 198 ++------- tests/pos/quote-array-type.scala | 7 + tests/pos/quote-lift-listof-expr.scala | 20 + 6 files changed, 470 insertions(+), 174 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala create mode 100644 tests/pos/quote-array-type.scala create mode 100644 tests/pos/quote-lift-listof-expr.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3b31ffe7b363..e40a9d122d8c 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -46,7 +46,8 @@ class Compiler { /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = List(new Pickler) :: // Generate TASTY info - List(new Staging) :: // Expand macros and turn quoted trees into explicit run-time data structures + List(new Staging) :: // Check quotation levels (while healing types) and expand macros + List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures Nil /** Phases dealing with the transformation from pickled trees to backend trees */ diff --git a/compiler/src/dotty/tools/dotc/quoted/TreeCleaner.scala b/compiler/src/dotty/tools/dotc/quoted/TreeCleaner.scala index 93c203fffb12..fc2365cea288 100644 --- a/compiler/src/dotty/tools/dotc/quoted/TreeCleaner.scala +++ b/compiler/src/dotty/tools/dotc/quoted/TreeCleaner.scala @@ -21,7 +21,7 @@ class TreeCleaner extends tpd.TreeMap { override def transform(tree: Tree)(implicit ctx: Context): Tree = { val tree0 = tree match { - case TypeDef(_, TypeBoundsTree(lo, hi)) if lo == hi => + case TypeDef(_, TypeBoundsTree(lo, hi)) if lo.tpe =:= hi.tpe => aliasesSyms = tree.symbol :: aliasesSyms aliasesTypes = lo.tpe :: aliasesTypes aliases(tree.symbol) = ref(lo.tpe.typeSymbol) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala new file mode 100644 index 000000000000..9266749454d7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -0,0 +1,414 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._ +import ast.Trees._ +import ast.{TreeTypeMap, untpd} +import util.Positions._ +import tasty.TreePickler.Hole +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import typer.Implicits.SearchFailureType + +import scala.collection.mutable +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.quoted._ +import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.util.SourcePosition + + +/** Translates quoted terms and types to `unpickle` method calls. + * + * Transforms top level quote + * ``` + * '{ ... + * val x1 = ??? + * val x2 = ??? + * ... + * ~{ ... '{ ... x1 ... x2 ...} ... } + * ... + * } + * ``` + * to + * ``` + * unpickle( + * [[ // PICKLED TASTY + * ... + * val x1 = ??? + * val x2 = ??? + * ... + * Hole(0 | x1, x2) + * ... + * ]], + * List( + * (args: Seq[Any]) => { + * val x1$1 = args(0).asInstanceOf[Expr[T]] + * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] + * ... + * { ... '{ ... x1$1.unary_~ ... x2$1.unary_~ ...} ... } + * } + * ) + * ) + * ``` + * and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`. + * + */ +class ReifyQuotes extends MacroTransformWithImplicits { + import tpd._ + import Staging._ + + override def phaseName: String = ReifyQuotes.name + + override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case tree: RefTree if !ctx.inInlineMethod => + assert(!tree.symbol.isQuote) + // assert(!tree.symbol.isSplice) // TODO widen ~ type references at stage 0? + assert(tree.symbol != defn.QuotedExpr_~) + case tree: Select if tree.symbol == defn.QuotedExpr_~ => + assert(Splicer.canBeSpliced(tree.qualifier)) + case _ => + } + } + + override def run(implicit ctx: Context): Unit = + if (ctx.compilationUnit.needsStaging) super.run + + protected def newTransformer(implicit ctx: Context): Transformer = + new Reifier(inQuote = false, null, 0, new LevelInfo, new Embedded, ctx) + + private class LevelInfo { + /** A map from locally defined symbols to the staging levels of their definitions */ + val levelOf = new mutable.HashMap[Symbol, Int] + + /** Register a reference defined in a quote but used in another quote nested in a splice. + * Returns a version of the reference that needs to be used in its place. + * '{ + * val x = ??? + * { ... '{ ... x ... } ... }.unary_~ + * } + * Eta expanding the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1` + * be created by some outer reifier. + * + * This transformation is only applied to definitions at staging level 1. + * + * See `isCaptured` + */ + val capturers = new mutable.HashMap[Symbol, Tree => Tree] + } + + /** The main transformer class + * @param inQuote we are within a `'(...)` context that is not shadowed by a nested `~(...)` + * @param outer the next outer reifier, null is this is the topmost transformer + * @param level the current level, where quotes add one and splices subtract one level. + * The initial level is 0, a level `l` where `l > 0` implies code has been quoted `l` times + * and `l == -1` is code inside a top level splice (in an inline method). + * @param levels a stacked map from symbols to the levels in which they were defined + * @param embedded a list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true` + * @param rctx the contex in the destination lifted lambda + */ + private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo, + val embedded: Embedded, val rctx: Context) extends ImplicitsTransformer { + import levels._ + assert(level >= -1) + + /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ + def nested(isQuote: Boolean)(implicit ctx: Context): Reifier = { + val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded + new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded, ctx) + } +// +// /** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */ +// def inSplice: Boolean = outer != null && !inQuote + + /** A stack of entered symbols, to be unwound after scope exit */ + var enteredSyms: List[Symbol] = Nil + + /** Enter staging level of symbol defined by `tree`, if applicable. */ + def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match { + case tree: DefTree => + val sym = tree.symbol + if ((sym.isClass || !sym.maybeOwner.isType) && !levelOf.contains(sym)) { + levelOf(sym) = level + enteredSyms = sym :: enteredSyms + } + case _ => + } + + /** Split `body` into a core and a list of embedded splices. + * Then if inside a splice, make a hole from these parts. + * If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or + * `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with + * core and splices as arguments. + */ + private def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val isType = quote.symbol eq defn.QuotedType_apply + if (body.symbol.isSplice) { + // simplify `'(~x)` to `x` and then transform it + val Select(splice, _) = body + transform(splice) + } + else if (level > 0) { + val body1 = nested(isQuote = true).transform(body) + // Keep quotes as trees to reduce pickled size and have a Expr.show without pickled quotes + if (isType) ref(defn.QuotedType_apply).appliedToType(body1.tpe.widen) + else ref(defn.QuotedExpr_apply).appliedToType(body1.tpe.widen).appliedTo(body1) + } + else body match { + case body: RefTree if isCaptured(body.symbol, level + 1) => + // Optimization: avoid the full conversion when capturing `x` + // in '{ x } to '{ x$1.unary_~ } and go directly to `x$1` + capturers(body.symbol)(body) + case _=> + val (body1, splices) = nested(isQuote = true).split(body) + if (level == 0 && !ctx.inInlineMethod) { + val body2 = + if (body1.isType) body1 + else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.pos), Nil, body1) + pickledQuote(body2, splices, body.tpe, isType).withPos(quote.pos) + } + else { + // In top-level splice in an inline def. Keep the tree as it is, it will be transformed at inline site. + body + } + } + } + + private def pickledQuote(body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(implicit ctx: Context) = { + def pickleAsValue[T](value: T) = + ref(defn.Unpickler_liftedExpr).appliedToType(originalTp.widen).appliedTo(Literal(Constant(value))) + def pickleAsTasty() = { + val meth = + if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp) + else ref(defn.Unpickler_unpickleExpr).appliedToType(deepDealias(originalTp.widen)) + meth.appliedTo( + liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType), + liftList(splices, defn.AnyType)) + } + if (splices.nonEmpty) pickleAsTasty() + else if (isType) { + def tag(tagName: String) = ref(defn.QuotedTypeModule).select(tagName.toTermName) + if (body.symbol == defn.UnitClass) tag("UnitTag") + else if (body.symbol == defn.BooleanClass) tag("BooleanTag") + else if (body.symbol == defn.ByteClass) tag("ByteTag") + else if (body.symbol == defn.CharClass) tag("CharTag") + else if (body.symbol == defn.ShortClass) tag("ShortTag") + else if (body.symbol == defn.IntClass) tag("IntTag") + else if (body.symbol == defn.LongClass) tag("LongTag") + else if (body.symbol == defn.FloatClass) tag("FloatTag") + else if (body.symbol == defn.DoubleClass) tag("DoubleTag") + else pickleAsTasty() + } + else Staging.toValue(body) match { + case Some(value) => pickleAsValue(value) + case _ => pickleAsTasty() + } + } + + /** If inside a quote, split the body of the splice into a core and a list of embedded quotes + * and make a hole from these parts. Otherwise issue an error, unless we + * are in the body of an inline method. + */ + private def splice(splice: Select)(implicit ctx: Context): Tree = { + if (level > 1) { + val body1 = nested(isQuote = false).transform(splice.qualifier) + body1.select(splice.name) + } + else if (level == 1) { + val (body1, quotes) = nested(isQuote = false).split(splice.qualifier) + val tpe = outer.embedded.getHoleType(splice) + val hole = makeHole(body1, quotes, tpe).withPos(splice.pos) + // We do not place add the inline marker for trees that where lifted as they come from the same file as their + // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. + // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. + // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). + if (splice.isType || outer.embedded.isLiftedSymbol(splice.qualifier.symbol)) hole + else Inlined(EmptyTree, Nil, hole) + } + else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call + assert(false) + splice + } + else if (!ctx.owner.isInlineMethod) { // level 0 outside an inline method +// if (splice.isTerm) +// ctx.error(i"splice outside quotes or inline method", splice.pos) + // some spliced types might be left as infered aliases to their underlying type + splice + + } + else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition + assert(false) +// nested(isQuote = false).split(splice.qualifier) // Just check PCP + splice + } + else { // level 0 inside an inline definition + assert(false) +// ctx.error("Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.".stripMargin, splice.pos) + splice + } + } + + /** Transforms the contents of a nested splice + * Assuming + * '{ + * val x = ??? + * val y = ??? + * { ... '{ ... x .. y ... } ... }.unary_~ + * } + * then the spliced subexpression + * { ... '{ ... x ... y ... } ... } + * will be transformed to + * (args: Seq[Any]) => { + * val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] + * val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] + * { ... '{ ... x$1.unary_~ ... y$1.unary_~ ... } ... } + * } + * + * See: `capture` + * + * At the same time register `embedded` trees `x` and `y` to place as arguments of the hole + * placed in the original code. + * '{ + * val x = ??? + * val y = ??? + * Hole(0 | x, y) + * } + */ + private def makeLambda(tree: Tree)(implicit ctx: Context): Tree = { + def body(arg: Tree)(implicit ctx: Context): Tree = { + var i = 0 + transformWithCapturer(tree)( + (captured: mutable.Map[Symbol, Tree]) => { + (tree: Tree) => { + def newCapture = { + val tpw = tree.tpe.widen match { + case tpw: MethodicType => tpw.toFunctionType() + case tpw => tpw + } + assert(tpw.isInstanceOf[ValueType]) + val argTpe = + if (tree.isType) defn.QuotedTypeType.appliedTo(tpw) + else defn.QuotedExprType.appliedTo(tpw) + val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe) + val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) + i += 1 + embedded.addTree(tree, capturedArg.symbol) + captured.put(tree.symbol, capturedArg) + capturedArg + } + ref(captured.getOrElseUpdate(tree.symbol, newCapture).symbol) + } + } + ) + } + /* Lambdas are generated outside the quote that is beeing reified (i.e. in outer.rctx.owner). + * In case the case that level == -1 the code is not in a quote, it is in an inline method, + * hence we should take that as owner directly. + */ + val lambdaOwner = if (level == -1) ctx.owner else outer.rctx.owner + + val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, deepDealias(tree.tpe.widen)) + val meth = ctx.newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe) + Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth)) + } + + private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(implicit ctx: Context): Tree = { + val captured = mutable.LinkedHashMap.empty[Symbol, Tree] + val captured2 = capturer(captured) + + outer.enteredSyms.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) + + val tree2 = transform(tree) + capturers --= outer.enteredSyms + + seq(captured.result().valuesIterator.toList, tree2) + } + + /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ + private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = + level == 1 && levelOf.get(sym).contains(1) && capturers.contains(sym) + + /** Transform `tree` and return the resulting tree and all `embedded` quotes + * or splices as a pair, after performing the `addTags` transform. + */ + private def split(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { + val tree1 = if (inQuote) transform(tree) else makeLambda(tree) + (tree1, embedded.getTrees) + } + + /** Register `body` as an `embedded` quote or splice + * and return a hole with `splices` as arguments and the given type `tpe`. + */ + private def makeHole(body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = { + val idx = embedded.addTree(body, NoSymbol) + Hole(idx, splices).withType(tpe).asInstanceOf[Hole] + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = + reporting.trace(i"reify $tree at $level", show = true) { + def mapOverTree(lastEntered: List[Symbol]) = + try super.transform(tree) + finally + while (enteredSyms ne lastEntered) { + levelOf -= enteredSyms.head + enteredSyms = enteredSyms.tail + } + tree match { + case Quoted(quotedTree) => + quotation(quotedTree, tree) + case tree: TypeTree if tree.tpe.typeSymbol.isSplice => + val splicedType = tree.tpe.stripTypeVar.asInstanceOf[TypeRef].prefix.termSymbol + splice(ref(splicedType).select(tpnme.UNARY_~).withPos(tree.pos)) + case tree: Select if tree.symbol.isSplice => + splice(tree) + case tree: RefTree if tree.symbol.is(Inline) && tree.symbol.is(Param) => + tree + case tree: RefTree if isCaptured(tree.symbol, level) => + val t = capturers(tree.symbol).apply(tree) + splice(t.select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) + case Block(stats, _) => + val last = enteredSyms + stats.foreach(markDef) + mapOverTree(last) + case CaseDef(pat, guard, body) => + val last = enteredSyms + // mark all bindings + new TreeTraverser { + def traverse(tree: Tree)(implicit ctx: Context): Unit = { + markDef(tree) + traverseChildren(tree) + } + }.traverse(pat) + mapOverTree(last) + case _: Import => + tree + case tree: DefDef if tree.symbol.is(Macro) && level > 0 => + EmptyTree + case tree: DefDef if tree.symbol.is(Macro) && level == 0 => + tree // Do nothin in this phase + case _ => + markDef(tree) + mapOverTree(enteredSyms) + } + } + + private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = { + list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => + acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) + } + } + + } + + private def deepDealias(tp: Type)(implicit ctx: Context): Type = new TypeMap() { + def apply(tp: Type): Type = mapOver(tp.dealias) + }.apply(tp) +} + + +object ReifyQuotes { + val name: String = "reifyQuotes" +} + diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 6a04d9bc01fa..19f44a7dc6d8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -15,47 +15,12 @@ import typer.Implicits.SearchFailureType import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.quoted._ -import dotty.tools.dotc.typer.Inliner import dotty.tools.dotc.util.SourcePosition -/** Translates quoted terms and types to `unpickle` method calls. - * Checks that the phase consistency principle (PCP) holds. - * - * - * Transforms top level quote - * ``` - * '{ ... - * val x1 = ??? - * val x2 = ??? - * ... - * ~{ ... '{ ... x1 ... x2 ...} ... } - * ... - * } - * ``` - * to - * ``` - * unpickle( - * [[ // PICKLED TASTY - * ... - * val x1 = ??? - * val x2 = ??? - * ... - * Hole(0 | x1, x2) - * ... - * ]], - * List( - * (args: Seq[Any]) => { - * val x1$1 = args(0).asInstanceOf[Expr[T]] - * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] - * ... - * { ... '{ ... x1$1.unary_~ ... x2$1.unary_~ ...} ... } - * } - * ) - * ) - * ``` - * and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`. +/** Checks that the phase consistency principle (PCP) holds. * + * TODO * * For macro definitions we assume that we have a single ~ directly as the RHS. * The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined. @@ -79,11 +44,11 @@ class Staging extends MacroTransformWithImplicits { override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { tree match { case tree: RefTree if !ctx.inInlineMethod => - assert(!tree.symbol.isQuote) +// assert(!tree.symbol.isQuote) // assert(!tree.symbol.isSplice) // TODO widen ~ type references at stage 0? - assert(tree.symbol != defn.QuotedExpr_~) +// assert(tree.symbol != defn.QuotedExpr_~) case tree: Select if tree.symbol == defn.QuotedExpr_~ => - assert(Splicer.canBeSpliced(tree.qualifier)) +// assert(Splicer.canBeSpliced(tree.qualifier)) case _ => } } @@ -174,8 +139,6 @@ class Staging extends MacroTransformWithImplicits { val rhs = transform(tag.select(tpnme.UNARY_~)) val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs) - val original = typeRef.symbol.asType - val local = ctx.newSymbol( owner = ctx.owner, name = UniqueName.fresh("T".toTermName).toTypeName, @@ -208,13 +171,22 @@ class Staging extends MacroTransformWithImplicits { tagsExplicitTypeDefsPairs.map(x => (x._1, x._2.symbol.typeRef)) ++ (itags.map(_._1) zip typeDefs.map(_.symbol.typeRef)) }.toMap - val tMap = new TypeMap() { - override def apply(tp: Type): Type = map.getOrElse(tp, mapOver(tp)) + val tpMap = new TypeMap() { + def apply(tp: Type): Type = map.getOrElse(tp, mapOver(tp)) + } + + def trMap(tree: Tree): Tree = { + if (tree.symbol ne defn.QuotedType_~) tree + else map.get(tree.tpe) match { + case Some(tp) => TypeTree(tp).withPos(tree.pos) // Replace an explicit ~t by its tag + case None => tree + } } Block(typeDefs ++ explicitTypeDefs, new TreeTypeMap( - typeMap = tMap, + treeMap = trMap, + typeMap = tpMap, substFrom = itags.map(_._1.symbol), substTo = typeDefs.map(_.symbol) ).apply(expr)) @@ -383,10 +355,10 @@ class Staging extends MacroTransformWithImplicits { case _=> val (body1, splices) = nested(isQuote = true).split(body) if (level == 0 && !ctx.inInlineMethod) { - val body2 = - if (body1.isType) body1 - else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.pos), Nil, body1) - pickledQuote(body2, splices, body.tpe, isType).withPos(quote.pos) + quote match { + case quote: Apply => cpy.Apply(quote)(quote.fun, body1 :: Nil) + case quote: TypeApply => cpy.TypeApply(quote)(quote.fun, body1 :: Nil) + } } else { // In top-level splice in an inline def. Keep the tree as it is, it will be transformed at inline site. @@ -395,37 +367,6 @@ class Staging extends MacroTransformWithImplicits { } } - private def pickledQuote(body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(implicit ctx: Context) = { - def pickleAsValue[T](value: T) = - ref(defn.Unpickler_liftedExpr).appliedToType(originalTp.widen).appliedTo(Literal(Constant(value))) - def pickleAsTasty() = { - val meth = - if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp) - else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen) - meth.appliedTo( - liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType), - liftList(splices, defn.AnyType)) - } - if (splices.nonEmpty) pickleAsTasty() - else if (isType) { - def tag(tagName: String) = ref(defn.QuotedTypeModule).select(tagName.toTermName) - if (body.symbol == defn.UnitClass) tag("UnitTag") - else if (body.symbol == defn.BooleanClass) tag("BooleanTag") - else if (body.symbol == defn.ByteClass) tag("ByteTag") - else if (body.symbol == defn.CharClass) tag("CharTag") - else if (body.symbol == defn.ShortClass) tag("ShortTag") - else if (body.symbol == defn.IntClass) tag("IntTag") - else if (body.symbol == defn.LongClass) tag("LongTag") - else if (body.symbol == defn.FloatClass) tag("FloatTag") - else if (body.symbol == defn.DoubleClass) tag("DoubleTag") - else pickleAsTasty() - } - else Staging.toValue(body) match { - case Some(value) => pickleAsValue(value) - case _ => pickleAsTasty() - } - } - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes * and make a hole from these parts. Otherwise issue an error, unless we * are in the body of an inline method. @@ -436,15 +377,8 @@ class Staging extends MacroTransformWithImplicits { body1.select(splice.name) } else if (level == 1) { - val (body1, quotes) = nested(isQuote = false).split(splice.qualifier) - val tpe = outer.embedded.getHoleType(splice) - val hole = makeHole(body1, quotes, tpe).withPos(splice.pos) - // We do not place add the inline marker for trees that where lifted as they come from the same file as their - // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. - // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. - // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). - if (splice.isType || outer.embedded.isLiftedSymbol(splice.qualifier.symbol)) hole - else Inlined(EmptyTree, Nil, hole) + val body1 = nested(isQuote = false).transform(splice.qualifier) + cpy.Select(splice)(body1, splice.name) } else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call val spliceCtx = ctx.outer // drop the last `inlineContext` @@ -466,82 +400,6 @@ class Staging extends MacroTransformWithImplicits { } } - /** Transforms the contents of a nested splice - * Assuming - * '{ - * val x = ??? - * val y = ??? - * { ... '{ ... x .. y ... } ... }.unary_~ - * } - * then the spliced subexpression - * { ... '{ ... x ... y ... } ... } - * will be transformed to - * (args: Seq[Any]) => { - * val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * { ... '{ ... x$1.unary_~ ... y$1.unary_~ ... } ... } - * } - * - * See: `capture` - * - * At the same time register `embedded` trees `x` and `y` to place as arguments of the hole - * placed in the original code. - * '{ - * val x = ??? - * val y = ??? - * Hole(0 | x, y) - * } - */ - private def makeLambda(tree: Tree)(implicit ctx: Context): Tree = { - def body(arg: Tree)(implicit ctx: Context): Tree = { - var i = 0 - transformWithCapturer(tree)( - (captured: mutable.Map[Symbol, Tree]) => { - (tree: Tree) => { - def newCapture = { - val tpw = tree.tpe.widen match { - case tpw: MethodicType => tpw.toFunctionType() - case tpw => tpw - } - assert(tpw.isInstanceOf[ValueType]) - val argTpe = - if (tree.isType) defn.QuotedTypeType.appliedTo(tpw) - else defn.QuotedExprType.appliedTo(tpw) - val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe) - val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) - i += 1 - embedded.addTree(tree, capturedArg.symbol) - captured.put(tree.symbol, capturedArg) - capturedArg - } - ref(captured.getOrElseUpdate(tree.symbol, newCapture).symbol) - } - } - ) - } - /* Lambdas are generated outside the quote that is beeing reified (i.e. in outer.rctx.owner). - * In case the case that level == -1 the code is not in a quote, it is in an inline method, - * hence we should take that as owner directly. - */ - val lambdaOwner = if (level == -1) ctx.owner else outer.rctx.owner - - val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen) - val meth = ctx.newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe) - Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth)) - } - - private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(implicit ctx: Context): Tree = { - val captured = mutable.LinkedHashMap.empty[Symbol, Tree] - val captured2 = capturer(captured) - - outer.enteredSyms.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) - - val tree2 = transform(tree) - capturers --= outer.enteredSyms - - seq(captured.result().valuesIterator.toList, tree2) - } - /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = level == 1 && levelOf.get(sym).contains(1) && capturers.contains(sym) @@ -550,7 +408,7 @@ class Staging extends MacroTransformWithImplicits { * or splices as a pair, after performing the `addTags` transform. */ private def split(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { - val tree1 = if (inQuote) addTags(transform(tree)) else makeLambda(tree) + val tree1 = if (inQuote) addTags(transform(tree)) else transform(tree) (tree1, embedded.getTrees) } @@ -620,18 +478,14 @@ class Staging extends MacroTransformWithImplicits { """.stripMargin, tree.rhs.pos) EmptyTree } + case tree: DefDef if tree.symbol.is(Macro) && level > 0 => + EmptyTree case _ => markDef(tree) checkLevel(mapOverTree(enteredSyms)) } } - private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = { - list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => - acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) - } - } - /** InlineSplice is used to detect cases where the expansion * consists of a (possibly multiple & nested) block or a sole expression. */ diff --git a/tests/pos/quote-array-type.scala b/tests/pos/quote-array-type.scala new file mode 100644 index 000000000000..42313dd9a749 --- /dev/null +++ b/tests/pos/quote-array-type.scala @@ -0,0 +1,7 @@ +import scala.quoted._ + +object Arrays { + def toExpr[T](implicit t: Type[T]): Expr[Array[List[T]]] = '{ + new Array[List[~t]](0) + } +} diff --git a/tests/pos/quote-lift-listof-expr.scala b/tests/pos/quote-lift-listof-expr.scala new file mode 100644 index 000000000000..447b2ff51f30 --- /dev/null +++ b/tests/pos/quote-lift-listof-expr.scala @@ -0,0 +1,20 @@ +package scala + +import quoted._ + +package object quoted2 { + + implicit class LiftExprOps[T](val x: T) extends AnyVal { + def toExpr(implicit ev: Liftable[T]): Expr[T] = ev.toExpr(x) + } + + implicit class ListOfExprOps[T](val list: List[Expr[T]]) extends AnyVal { + def toExprOfList(implicit ev: Type[T]): Expr[List[T]] = { + def rec(list: List[Expr[T]]): Expr[List[T]] = list match { + case x :: xs => '{ (~x) :: (~rec(xs)) } + case Nil => '(Nil) + } + rec(list) + } + } +} From 5711c85bb361334c5f4e8186d2a58b18c28855a2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 16:33:06 +0100 Subject: [PATCH 02/14] Cleanup --- .../tools/dotc/transform/ReifyQuotes.scala | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 9266749454d7..93ffdb83e321 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -222,30 +222,13 @@ class ReifyQuotes extends MacroTransformWithImplicits { val hole = makeHole(body1, quotes, tpe).withPos(splice.pos) // We do not place add the inline marker for trees that where lifted as they come from the same file as their // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. - // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. + // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). if (splice.isType || outer.embedded.isLiftedSymbol(splice.qualifier.symbol)) hole else Inlined(EmptyTree, Nil, hole) } - else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call - assert(false) - splice - } - else if (!ctx.owner.isInlineMethod) { // level 0 outside an inline method -// if (splice.isTerm) -// ctx.error(i"splice outside quotes or inline method", splice.pos) - // some spliced types might be left as infered aliases to their underlying type - splice - - } - else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition - assert(false) -// nested(isQuote = false).split(splice.qualifier) // Just check PCP - splice - } - else { // level 0 inside an inline definition - assert(false) -// ctx.error("Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.".stripMargin, splice.pos) + else { + assert(level == 0) splice } } From 9905bf36c9c7bc86b1879482c2c2e8e795df4870 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 16:35:09 +0100 Subject: [PATCH 03/14] Remove levelOf from ReifyQuotes --- .../src/dotty/tools/dotc/transform/ReifyQuotes.scala | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 93ffdb83e321..2ee64f75b617 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -80,8 +80,6 @@ class ReifyQuotes extends MacroTransformWithImplicits { new Reifier(inQuote = false, null, 0, new LevelInfo, new Embedded, ctx) private class LevelInfo { - /** A map from locally defined symbols to the staging levels of their definitions */ - val levelOf = new mutable.HashMap[Symbol, Int] /** Register a reference defined in a quote but used in another quote nested in a splice. * Returns a version of the reference that needs to be used in its place. @@ -119,9 +117,6 @@ class ReifyQuotes extends MacroTransformWithImplicits { val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded, ctx) } -// -// /** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */ -// def inSplice: Boolean = outer != null && !inQuote /** A stack of entered symbols, to be unwound after scope exit */ var enteredSyms: List[Symbol] = Nil @@ -130,8 +125,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match { case tree: DefTree => val sym = tree.symbol - if ((sym.isClass || !sym.maybeOwner.isType) && !levelOf.contains(sym)) { - levelOf(sym) = level + if ((sym.isClass || !sym.maybeOwner.isType)) { enteredSyms = sym :: enteredSyms } case _ => @@ -311,7 +305,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = - level == 1 && levelOf.get(sym).contains(1) && capturers.contains(sym) + level == 1 && capturers.contains(sym) /** Transform `tree` and return the resulting tree and all `embedded` quotes * or splices as a pair, after performing the `addTags` transform. @@ -335,7 +329,6 @@ class ReifyQuotes extends MacroTransformWithImplicits { try super.transform(tree) finally while (enteredSyms ne lastEntered) { - levelOf -= enteredSyms.head enteredSyms = enteredSyms.tail } tree match { From 0282c21df03f3c922c679bfde9c3edff2c88538a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 17:02:57 +0100 Subject: [PATCH 04/14] Cleanup --- .../tools/dotc/transform/ReifyQuotes.scala | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 2ee64f75b617..c5694b6d4d8d 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -77,9 +77,9 @@ class ReifyQuotes extends MacroTransformWithImplicits { if (ctx.compilationUnit.needsStaging) super.run protected def newTransformer(implicit ctx: Context): Transformer = - new Reifier(inQuote = false, null, 0, new LevelInfo, new Embedded, ctx) + new Reifier(inQuote = false, null, 0, new Info, new Embedded, ctx) - private class LevelInfo { + private class Info { /** Register a reference defined in a quote but used in another quote nested in a splice. * Returns a version of the reference that needs to be used in its place. @@ -95,6 +95,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { * See `isCaptured` */ val capturers = new mutable.HashMap[Symbol, Tree => Tree] + } /** The main transformer class @@ -105,17 +106,17 @@ class ReifyQuotes extends MacroTransformWithImplicits { * and `l == -1` is code inside a top level splice (in an inline method). * @param levels a stacked map from symbols to the levels in which they were defined * @param embedded a list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true` - * @param rctx the contex in the destination lifted lambda + * @param rctx the context in the destination lifted lambda */ - private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo, + private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, info: Info, val embedded: Embedded, val rctx: Context) extends ImplicitsTransformer { - import levels._ + import info._ assert(level >= -1) /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ def nested(isQuote: Boolean)(implicit ctx: Context): Reifier = { val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded - new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded, ctx) + new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, info, nestedEmbedded, ctx) } /** A stack of entered symbols, to be unwound after scope exit */ @@ -125,7 +126,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match { case tree: DefTree => val sym = tree.symbol - if ((sym.isClass || !sym.maybeOwner.isType)) { + if (sym.isClass || !sym.maybeOwner.isType) { enteredSyms = sym :: enteredSyms } case _ => @@ -327,10 +328,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { reporting.trace(i"reify $tree at $level", show = true) { def mapOverTree(lastEntered: List[Symbol]) = try super.transform(tree) - finally - while (enteredSyms ne lastEntered) { - enteredSyms = enteredSyms.tail - } + finally enteredSyms = lastEntered tree match { case Quoted(quotedTree) => quotation(quotedTree, tree) @@ -348,7 +346,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { val last = enteredSyms stats.foreach(markDef) mapOverTree(last) - case CaseDef(pat, guard, body) => + case CaseDef(pat, _, _) => val last = enteredSyms // mark all bindings new TreeTraverser { @@ -363,7 +361,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { case tree: DefDef if tree.symbol.is(Macro) && level > 0 => EmptyTree case tree: DefDef if tree.symbol.is(Macro) && level == 0 => - tree // Do nothin in this phase + tree // Do nothing in this phase case _ => markDef(tree) mapOverTree(enteredSyms) From dc0bf3c8f533e1ddd5be4c3ac3c07b3ea27acb5b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 17:18:56 +0100 Subject: [PATCH 05/14] Make enteredSyms private --- .../src/dotty/tools/dotc/transform/ReifyQuotes.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index c5694b6d4d8d..4b48dcdddd5a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -120,7 +120,10 @@ class ReifyQuotes extends MacroTransformWithImplicits { } /** A stack of entered symbols, to be unwound after scope exit */ - var enteredSyms: List[Symbol] = Nil + private var enteredSyms: List[Symbol] = Nil + + /** A stack of entered symbols, to be unwound after scope exit */ + def getEnteredSyms: List[Symbol] = enteredSyms /** Enter staging level of symbol defined by `tree`, if applicable. */ def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match { @@ -296,10 +299,10 @@ class ReifyQuotes extends MacroTransformWithImplicits { val captured = mutable.LinkedHashMap.empty[Symbol, Tree] val captured2 = capturer(captured) - outer.enteredSyms.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) + outer.getEnteredSyms.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) val tree2 = transform(tree) - capturers --= outer.enteredSyms + capturers --= outer.getEnteredSyms seq(captured.result().valuesIterator.toList, tree2) } From af2e4b0a6f9a434ec33424ea68582c990b8052bf Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 17:24:39 +0100 Subject: [PATCH 06/14] Deduplicate code --- compiler/src/dotty/tools/dotc/transform/Staging.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 19f44a7dc6d8..e20818cfa5b0 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -372,14 +372,10 @@ class Staging extends MacroTransformWithImplicits { * are in the body of an inline method. */ private def splice(splice: Select)(implicit ctx: Context): Tree = { - if (level > 1) { + if (level >= 1) { val body1 = nested(isQuote = false).transform(splice.qualifier) body1.select(splice.name) } - else if (level == 1) { - val body1 = nested(isQuote = false).transform(splice.qualifier) - cpy.Select(splice)(body1, splice.name) - } else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call val spliceCtx = ctx.outer // drop the last `inlineContext` val pos: SourcePosition = Decorators.sourcePos(enclosingInlineds.head.pos)(spliceCtx) From 40621e8d472923daa6a1b13d02e4fa05a02899c3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 18:04:49 +0100 Subject: [PATCH 07/14] Remove logic from quote reification --- .../dotty/tools/dotc/transform/Staging.scala | 84 ++++--------------- 1 file changed, 18 insertions(+), 66 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index e20818cfa5b0..68ccb2857520 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -42,15 +42,7 @@ class Staging extends MacroTransformWithImplicits { override def phaseName: String = Staging.name override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = { - tree match { - case tree: RefTree if !ctx.inInlineMethod => -// assert(!tree.symbol.isQuote) - // assert(!tree.symbol.isSplice) // TODO widen ~ type references at stage 0? -// assert(tree.symbol != defn.QuotedExpr_~) - case tree: Select if tree.symbol == defn.QuotedExpr_~ => -// assert(Splicer.canBeSpliced(tree.qualifier)) - case _ => - } + // TODO check PCP } override def run(implicit ctx: Context): Unit = @@ -62,21 +54,6 @@ class Staging extends MacroTransformWithImplicits { private class LevelInfo { /** A map from locally defined symbols to the staging levels of their definitions */ val levelOf = new mutable.HashMap[Symbol, Int] - - /** Register a reference defined in a quote but used in another quote nested in a splice. - * Returns a version of the reference that needs to be used in its place. - * '{ - * val x = ??? - * { ... '{ ... x ... } ... }.unary_~ - * } - * Eta expanding the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1` - * be created by some outer reifier. - * - * This transformation is only applied to definitions at staging level 1. - * - * See `isCaptured` - */ - val capturers = new mutable.HashMap[Symbol, Tree => Tree] } /** The main transformer class @@ -263,8 +240,6 @@ class Staging extends MacroTransformWithImplicits { else i"${sym.name}.this" if (!isThis && sym.maybeOwner.isType && !sym.is(Param)) check(sym.owner, sym.owner.thisType, pos) - else if (level == 1 && sym.isType && sym.is(Param) && sym.owner.isInlineMethod && !outer.isRoot) - importedTags(sym.typeRef) = capturers(sym)(ref(sym)) else if (sym.exists && !sym.isStaticOwner && !levelOK(sym)) for (errMsg <- tryHeal(tp, pos)) ctx.error(em"""access to $symStr from wrong staging level: @@ -347,23 +322,18 @@ class Staging extends MacroTransformWithImplicits { if (isType) ref(defn.QuotedType_apply).appliedToType(body1.tpe.widen) else ref(defn.QuotedExpr_apply).appliedToType(body1.tpe.widen).appliedTo(body1) } - else body match { - case body: RefTree if isCaptured(body.symbol, level + 1) => - // Optimization: avoid the full conversion when capturing `x` - // in '{ x } to '{ x$1.unary_~ } and go directly to `x$1` - capturers(body.symbol)(body) - case _=> - val (body1, splices) = nested(isQuote = true).split(body) - if (level == 0 && !ctx.inInlineMethod) { - quote match { - case quote: Apply => cpy.Apply(quote)(quote.fun, body1 :: Nil) - case quote: TypeApply => cpy.TypeApply(quote)(quote.fun, body1 :: Nil) - } - } - else { - // In top-level splice in an inline def. Keep the tree as it is, it will be transformed at inline site. - body + else { + val body1 = nested(isQuote = true).transformAndAddTags(body) + if (level == 0 && !ctx.inInlineMethod) { + quote match { + case quote: Apply => cpy.Apply(quote)(quote.fun, body1 :: Nil) + case quote: TypeApply => cpy.TypeApply(quote)(quote.fun, body1 :: Nil) } + } + else { + // In top-level splice in an inline def. Keep the tree as it is, it will be transformed at inline site. + body + } } } @@ -387,7 +357,7 @@ class Staging extends MacroTransformWithImplicits { splice } else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition - nested(isQuote = false).split(splice.qualifier) // Just check PCP + nested(isQuote = false).transform(splice.qualifier) // Just check PCP splice } else { // level 0 inside an inline definition @@ -396,24 +366,10 @@ class Staging extends MacroTransformWithImplicits { } } - /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ - private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = - level == 1 && levelOf.get(sym).contains(1) && capturers.contains(sym) - - /** Transform `tree` and return the resulting tree and all `embedded` quotes - * or splices as a pair, after performing the `addTags` transform. - */ - private def split(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { - val tree1 = if (inQuote) addTags(transform(tree)) else transform(tree) - (tree1, embedded.getTrees) - } - - /** Register `body` as an `embedded` quote or splice - * and return a hole with `splices` as arguments and the given type `tpe`. - */ - private def makeHole(body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = { - val idx = embedded.addTree(body, NoSymbol) - Hole(idx, splices).withType(tpe).asInstanceOf[Hole] + /** Transform `tree` followed by `addTags` transform. */ + private def transformAndAddTags(tree: Tree)(implicit ctx: Context): Tree = { + assert(inQuote) + addTags(transform(tree)) } override def transform(tree: Tree)(implicit ctx: Context): Tree = @@ -435,14 +391,11 @@ class Staging extends MacroTransformWithImplicits { splice(tree) case tree: RefTree if tree.symbol.is(Inline) && tree.symbol.is(Param) => tree - case tree: RefTree if isCaptured(tree.symbol, level) => - val t = capturers(tree.symbol).apply(tree) - splice(t.select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) case Block(stats, _) => val last = enteredSyms stats.foreach(markDef) mapOverTree(last) - case CaseDef(pat, guard, body) => + case CaseDef(pat, _, _) => val last = enteredSyms // mark all bindings new TreeTraverser { @@ -498,7 +451,6 @@ class Staging extends MacroTransformWithImplicits { } object Staging { - import tpd._ val name: String = "staging" From 655a24fd5bdb11ff3aaa2dbee325eea92c39e2d9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 18:15:10 +0100 Subject: [PATCH 08/14] Move Embedded to ReifyQuotes --- .../tools/dotc/transform/ReifyQuotes.scala | 38 ++++++++++++++++- .../dotty/tools/dotc/transform/Staging.scala | 42 ++----------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 4b48dcdddd5a..809ac81d40dc 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -57,7 +57,7 @@ import dotty.tools.dotc.util.SourcePosition */ class ReifyQuotes extends MacroTransformWithImplicits { import tpd._ - import Staging._ + import ReifyQuotes._ override def phaseName: String = ReifyQuotes.name @@ -199,7 +199,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { else if (body.symbol == defn.DoubleClass) tag("DoubleTag") else pickleAsTasty() } - else Staging.toValue(body) match { + else ReifyQuotes.toValue(body) match { case Some(value) => pickleAsValue(value) case _ => pickleAsTasty() } @@ -386,6 +386,40 @@ class ReifyQuotes extends MacroTransformWithImplicits { object ReifyQuotes { + val name: String = "reifyQuotes" + + def toValue(tree: tpd.Tree): Option[Any] = tree match { + case Literal(Constant(c)) => Some(c) + case Block(Nil, e) => toValue(e) + case Inlined(_, Nil, e) => toValue(e) + case _ => None + } + + class Embedded(trees: mutable.ListBuffer[tpd.Tree] = mutable.ListBuffer.empty, map: mutable.Map[Symbol, tpd.Tree] = mutable.Map.empty) { + /** Adds the tree and returns it's index */ + def addTree(tree: tpd.Tree, liftedSym: Symbol): Int = { + trees += tree + if (liftedSym ne NoSymbol) + map.put(liftedSym, tree) + trees.length - 1 + } + + /** Type used for the hole that will replace this splice */ + def getHoleType(splice: tpd.Select)(implicit ctx: Context): Type = { + // For most expressions the splice.tpe but there are some types that are lost by lifting + // that can be recoverd from the original tree. Currently the cases are: + // * Method types: the splice represents a method reference + map.get(splice.qualifier.symbol).map(_.tpe.widen).getOrElse(splice.tpe) + } + + def isLiftedSymbol(sym: Symbol)(implicit ctx: Context): Boolean = map.contains(sym) + + /** Get the list of embedded trees */ + def getTrees: List[tpd.Tree] = trees.toList + + override def toString: String = s"Embedded($trees, $map)" + + } } diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 68ccb2857520..1522640b3215 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -49,7 +49,7 @@ class Staging extends MacroTransformWithImplicits { if (ctx.compilationUnit.needsStaging) super.run protected def newTransformer(implicit ctx: Context): Transformer = - new Reifier(inQuote = false, null, 0, new LevelInfo, new Embedded, ctx) + new Reifier(inQuote = false, null, 0, new LevelInfo, ctx) private class LevelInfo { /** A map from locally defined symbols to the staging levels of their definitions */ @@ -63,18 +63,16 @@ class Staging extends MacroTransformWithImplicits { * The initial level is 0, a level `l` where `l > 0` implies code has been quoted `l` times * and `l == -1` is code inside a top level splice (in an inline method). * @param levels a stacked map from symbols to the levels in which they were defined - * @param embedded a list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true` * @param rctx the contex in the destination lifted lambda */ private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo, - val embedded: Embedded, val rctx: Context) extends ImplicitsTransformer { + val rctx: Context) extends ImplicitsTransformer { import levels._ assert(level >= -1) /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ def nested(isQuote: Boolean)(implicit ctx: Context): Reifier = { - val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded - new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded, ctx) + new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, ctx) } /** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */ @@ -451,39 +449,5 @@ class Staging extends MacroTransformWithImplicits { } object Staging { - val name: String = "staging" - - def toValue(tree: tpd.Tree): Option[Any] = tree match { - case Literal(Constant(c)) => Some(c) - case Block(Nil, e) => toValue(e) - case Inlined(_, Nil, e) => toValue(e) - case _ => None - } - - class Embedded(trees: mutable.ListBuffer[tpd.Tree] = mutable.ListBuffer.empty, map: mutable.Map[Symbol, tpd.Tree] = mutable.Map.empty) { - /** Adds the tree and returns it's index */ - def addTree(tree: tpd.Tree, liftedSym: Symbol): Int = { - trees += tree - if (liftedSym ne NoSymbol) - map.put(liftedSym, tree) - trees.length - 1 - } - - /** Type used for the hole that will replace this splice */ - def getHoleType(splice: tpd.Select)(implicit ctx: Context): Type = { - // For most expressions the splice.tpe but there are some types that are lost by lifting - // that can be recoverd from the original tree. Currently the cases are: - // * Method types: the splice represents a method reference - map.get(splice.qualifier.symbol).map(_.tpe.widen).getOrElse(splice.tpe) - } - - def isLiftedSymbol(sym: Symbol)(implicit ctx: Context): Boolean = map.contains(sym) - - /** Get the list of embedded trees */ - def getTrees: List[tpd.Tree] = trees.toList - - override def toString: String = s"Embedded($trees, $map)" - - } } From f8aa9b5b290e76f6e59bbdce86d22b70db217003 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 18:26:44 +0100 Subject: [PATCH 09/14] Remove root context --- .../src/dotty/tools/dotc/transform/Staging.scala | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 1522640b3215..c6a3e513df95 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -49,7 +49,7 @@ class Staging extends MacroTransformWithImplicits { if (ctx.compilationUnit.needsStaging) super.run protected def newTransformer(implicit ctx: Context): Transformer = - new Reifier(inQuote = false, null, 0, new LevelInfo, ctx) + new Reifier(inQuote = false, null, 0, new LevelInfo) private class LevelInfo { /** A map from locally defined symbols to the staging levels of their definitions */ @@ -63,24 +63,16 @@ class Staging extends MacroTransformWithImplicits { * The initial level is 0, a level `l` where `l > 0` implies code has been quoted `l` times * and `l == -1` is code inside a top level splice (in an inline method). * @param levels a stacked map from symbols to the levels in which they were defined - * @param rctx the contex in the destination lifted lambda */ - private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo, - val rctx: Context) extends ImplicitsTransformer { + private class Reifier(inQuote: Boolean, val outer: Reifier, level: Int, levels: LevelInfo) extends ImplicitsTransformer { import levels._ assert(level >= -1) /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ def nested(isQuote: Boolean)(implicit ctx: Context): Reifier = { - new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, ctx) + new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels) } - /** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */ - def inSplice: Boolean = outer != null && !inQuote - - /** We are not in a `~(...)` or a `'(...)` */ - def isRoot: Boolean = outer == null - /** A map from type ref T to expressions of type `quoted.Type[T]`". * These will be turned into splices using `addTags` and represent type variables * that can be possibly healed. From da1fbd158ed441d43214a10fc7ab26d3e91f3d01 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 18:29:31 +0100 Subject: [PATCH 10/14] Rename Staging.Reifier to Stager --- compiler/src/dotty/tools/dotc/transform/Staging.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index c6a3e513df95..b05735e667bd 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -49,7 +49,7 @@ class Staging extends MacroTransformWithImplicits { if (ctx.compilationUnit.needsStaging) super.run protected def newTransformer(implicit ctx: Context): Transformer = - new Reifier(inQuote = false, null, 0, new LevelInfo) + new Stager(inQuote = false, null, 0, new LevelInfo) private class LevelInfo { /** A map from locally defined symbols to the staging levels of their definitions */ @@ -64,13 +64,13 @@ class Staging extends MacroTransformWithImplicits { * and `l == -1` is code inside a top level splice (in an inline method). * @param levels a stacked map from symbols to the levels in which they were defined */ - private class Reifier(inQuote: Boolean, val outer: Reifier, level: Int, levels: LevelInfo) extends ImplicitsTransformer { + private class Stager(inQuote: Boolean, val outer: Stager, level: Int, levels: LevelInfo) extends ImplicitsTransformer { import levels._ assert(level >= -1) /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ - def nested(isQuote: Boolean)(implicit ctx: Context): Reifier = { - new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels) + def nested(isQuote: Boolean)(implicit ctx: Context): Stager = { + new Stager(isQuote, this, if (isQuote) level + 1 else level - 1, levels) } /** A map from type ref T to expressions of type `quoted.Type[T]`". @@ -272,7 +272,7 @@ class Staging extends MacroTransformWithImplicits { * * type T' = ~quoted.Type[T] * - * to the quoted text and rename T to T' in it. This is done later in `reify` via + * to the quoted text and rename T to T' in it. This is done later via * `addTags`. `checkLevel` itself only records what needs to be done in the * `typeTagOfRef` field of the current `Splice` structure. */ From 67c30ce77355956f9193fd0b2b5b5c6a44058614 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 13 Jan 2019 18:40:08 +0100 Subject: [PATCH 11/14] Remove optimization that is also done later in ReifyQuotes --- compiler/src/dotty/tools/dotc/transform/Staging.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index b05735e667bd..e3045ebc9675 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -301,12 +301,7 @@ class Staging extends MacroTransformWithImplicits { */ private def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { val isType = quote.symbol eq defn.QuotedType_apply - if (body.symbol.isSplice) { - // simplify `'(~x)` to `x` and then transform it - val Select(splice, _) = body - transform(splice) - } - else if (level > 0) { + if (level > 0) { val body1 = nested(isQuote = true).transform(body) // Keep quotes as trees to reduce pickled size and have a Expr.show without pickled quotes if (isType) ref(defn.QuotedType_apply).appliedToType(body1.tpe.widen) From ebe73b38e4eeacff2622938b7cfe4f2e0b59f151 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 14 Jan 2019 13:05:16 +0100 Subject: [PATCH 12/14] Remove unnecessary transformation --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 3 --- compiler/src/dotty/tools/dotc/transform/Staging.scala | 7 ++----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 809ac81d40dc..2cd288eddd5a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -335,9 +335,6 @@ class ReifyQuotes extends MacroTransformWithImplicits { tree match { case Quoted(quotedTree) => quotation(quotedTree, tree) - case tree: TypeTree if tree.tpe.typeSymbol.isSplice => - val splicedType = tree.tpe.stripTypeVar.asInstanceOf[TypeRef].prefix.termSymbol - splice(ref(splicedType).select(tpnme.UNARY_~).withPos(tree.pos)) case tree: Select if tree.symbol.isSplice => splice(tree) case tree: RefTree if tree.symbol.is(Inline) && tree.symbol.is(Param) => diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index e3045ebc9675..b844f3f2ab02 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -18,9 +18,9 @@ import dotty.tools.dotc.core.quoted._ import dotty.tools.dotc.util.SourcePosition -/** Checks that the phase consistency principle (PCP) holds. +/** Checks that the phase consistency principle (PCP) holds, heals types and expand macros. * - * TODO + * Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`. * * For macro definitions we assume that we have a single ~ directly as the RHS. * The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined. @@ -369,9 +369,6 @@ class Staging extends MacroTransformWithImplicits { tree match { case Quoted(quotedTree) => quotation(quotedTree, tree) - case tree: TypeTree if tree.tpe.typeSymbol.isSplice => - val splicedType = tree.tpe.stripTypeVar.asInstanceOf[TypeRef].prefix.termSymbol - splice(ref(splicedType).select(tpnme.UNARY_~).withPos(tree.pos)) case tree: Select if tree.symbol.isSplice => splice(tree) case tree: RefTree if tree.symbol.is(Inline) && tree.symbol.is(Param) => From 40ba19ad6277c063aada9169cb40619af9e265b3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 18 Jan 2019 07:40:43 +0100 Subject: [PATCH 13/14] Move Staging before Pickler --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 7 +- .../tools/dotc/tastyreflect/TreeOpsImpl.scala | 2 +- .../dotty/tools/dotc/transform/Staging.scala | 94 ++++++++++++++----- library/src/scala/tasty/reflect/TreeOps.scala | 2 +- tests/plugins/neg/divideZero/plugin_1.scala | 4 +- 6 files changed, 82 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index e40a9d122d8c..f3d3ce17d1db 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -45,8 +45,8 @@ class Compiler { /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = - List(new Pickler) :: // Generate TASTY info List(new Staging) :: // Check quotation levels (while healing types) and expand macros + List(new Pickler) :: // Generate TASTY info List(new ReifyQuotes) :: // Turn quoted trees into explicit run-time data structures Nil diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index 25269c17e863..d6ef6770314d 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -162,7 +162,7 @@ class TreeTypeMap( assert(!to.exists(substFrom contains _)) assert(!from.exists(newOwners contains _)) assert(!to.exists(oldOwners contains _)) - new TreeTypeMap( + newTreeTypeMap( typeMap, treeMap, from ++ oldOwners, @@ -171,6 +171,11 @@ class TreeTypeMap( to ++ substTo) } + protected def newTreeTypeMap(typeMap: Type => Type, treeMap: tpd.Tree => tpd.Tree, + oldOwners: List[Symbol], newOwners: List[Symbol], substFrom: List[Symbol], substTo: List[Symbol]) = { + new TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) + } + /** Apply `typeMap` and `ownerMap` to given symbols `syms` * and return a treemap that contains the substitution * between original and mapped symbols. diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala index 49b221a22947..d3ef6c6c2f7f 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala @@ -775,7 +775,7 @@ trait TreeOpsImpl extends scala.tasty.reflect.TreeOps with CoreImpl with Helpers tpd.Return(expr, ctx.owner) def copy(original: Tree)(expr: Term)(implicit ctx: Context): Return = - tpd.cpy.Return(original)(expr, tpd.EmptyTree) + tpd.cpy.Return(original)(expr, tpd.ref(ctx.owner)) def unapply(x: Term)(implicit ctx: Context): Option[Term] = x match { case x: tpd.Return => Some(x.expr) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index b844f3f2ab02..d42182b4cc10 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -1,21 +1,24 @@ -package dotty.tools.dotc -package transform - -import core._ -import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._ -import ast.Trees._ -import ast.{TreeTypeMap, untpd} -import util.Positions._ -import tasty.TreePickler.Hole -import SymUtils._ -import NameKinds._ -import dotty.tools.dotc.ast.tpd -import typer.Implicits.SearchFailureType +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{TreeTypeMap, tpd, untpd} +import dotty.tools.dotc.core.Constants._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.tasty.TreePickler.Hole +import dotty.tools.dotc.reporting +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.typer.Implicits.SearchFailureType +import dotty.tools.dotc.util.Positions._ +import dotty.tools.dotc.util.SourcePosition import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.quoted._ -import dotty.tools.dotc.util.SourcePosition /** Checks that the phase consistency principle (PCP) holds, heals types and expand macros. @@ -83,7 +86,7 @@ class Staging extends MacroTransformWithImplicits { * These will be turned into splices using `addTags` and represent types spliced * explicitly. */ - val explicitTags = new mutable.LinkedHashSet[TypeRef]() + val explicitTags = new mutable.LinkedHashSet[(TypeRef, Position)]() /** A stack of entered symbols, to be unwound after scope exit */ var enteredSyms: List[Symbol] = Nil @@ -111,7 +114,8 @@ class Staging extends MacroTransformWithImplicits { name = UniqueName.fresh("T".toTermName).toTypeName, flags = Synthetic, info = TypeAlias(tag.tpe.select(tpnme.UNARY_~)), - coord = typeRef.prefix.termSymbol.coord).asType + coord = alias.pos + ).asType ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) } @@ -126,11 +130,11 @@ class Staging extends MacroTransformWithImplicits { importedTags.clear() // The tree of the tag for each tag comes from a type ref e.g., ~t - val explicitTypeDefs = for (tref <- explicitTags) yield { - val tag = ref(tref.prefix.termSymbol) + val explicitTypeDefs = for ((tref, pos) <- explicitTags) yield { + val tag = ref(tref.prefix.termSymbol).withPos(pos) mkTagSymbolAndAssignType(tref, tag) } - val tagsExplicitTypeDefsPairs = explicitTags.zip(explicitTypeDefs) + val tagsExplicitTypeDefsPairs = explicitTags.map(_._1).zip(explicitTypeDefs) explicitTags.clear() // Maps type splices to type references of tags e.g., ~t -> some type T$1 @@ -150,8 +154,52 @@ class Staging extends MacroTransformWithImplicits { } } + /** Type tree map that does not tag type at level 0 */ + class QuoteTreeTypeMap( + typeMap: Type => Type = IdentityTypeMap, + treeMap: tpd.Tree => tpd.Tree = identity _, + oldOwners: List[Symbol] = Nil, + newOwners: List[Symbol] = Nil, + substFrom: List[Symbol] = Nil, + substTo: List[Symbol] = Nil + )(implicit ctx: Context) extends TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) { self => + + protected var level = 1 // TODO use context to keep track of the level + + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + if (level == 0) { + // Keep transforming but do not replace insert the taged types. Types in nested quotes are also not taged. + val (sFrom, sTo) = substFrom.zip(substTo).filterNot(_._2.is(Synthetic)).unzip // TODO Syntetic is probably not enugh to distinguish added types + new TreeTypeMap(typeMap, treeMap, + oldOwners, newOwners, + sFrom, sTo + ).transform(tree) + } + else if (tree.symbol.isSplice) { + level -= 1 + try super.transform(tree) + finally level += 1 + } else if (tree.symbol.isQuote) { + level += 1 + try super.transform(tree) + finally level -= 1 + } + else super.transform(tree) + + } + + protected override def newTreeTypeMap(typeMap: Type => Type, treeMap: tpd.Tree => tpd.Tree, + oldOwners: List[Symbol], newOwners: List[Symbol], + substFrom: List[Symbol], substTo: List[Symbol]) = { + new QuoteTreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) { + level = self.level + } + } + + } + Block(typeDefs ++ explicitTypeDefs, - new TreeTypeMap( + new QuoteTreeTypeMap( treeMap = trMap, typeMap = tpMap, substFrom = itags.map(_._1.symbol), @@ -243,7 +291,7 @@ class Staging extends MacroTransformWithImplicits { tp match { case tp: TypeRef if tp.symbol.isSplice => if (inQuote) { - explicitTags += tp + explicitTags += Tuple2(tp, pos) outer.checkType(pos).foldOver(acc, tp) } else { @@ -333,7 +381,7 @@ class Staging extends MacroTransformWithImplicits { } else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call val spliceCtx = ctx.outer // drop the last `inlineContext` - val pos: SourcePosition = Decorators.sourcePos(enclosingInlineds.head.pos)(spliceCtx) + val pos: SourcePosition = sourcePos(enclosingInlineds.head.pos)(spliceCtx) val evaluatedSplice = Splicer.splice(splice.qualifier, pos, macroClassLoader)(spliceCtx).withPos(splice.pos) if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice) } @@ -396,7 +444,7 @@ class Staging extends MacroTransformWithImplicits { tree.rhs match { case InlineSplice(_) => mapOverTree(enteredSyms) // Ignore output, only check PCP - cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + tree case _ => ctx.error( """Malformed macro. diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index be45b19e8f02..33245e23be9f 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -637,7 +637,7 @@ trait TreeOps extends Core { /** Scala local `return` */ val Return: ReturnModule - abstract class ReturnModule { + abstract class ReturnModule { // TODO should we expose explicitly the returns `from`? /** Creates `return ` */ def apply(expr: Term)(implicit ctx: Context): Return diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 97b3dfa22ee2..f9ff7612b119 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -8,7 +8,7 @@ import transform.MegaPhase.MiniPhase import Decorators._ import Symbols.Symbol import Constants.Constant -import transform.{Pickler, Staging} +import transform.{Pickler, ReifyQuotes} import StdNames._ class DivideZero extends PluginPhase with StandardPlugin { @@ -18,7 +18,7 @@ class DivideZero extends PluginPhase with StandardPlugin { val phaseName = name override val runsAfter = Set(Pickler.name) - override val runsBefore = Set(Staging.name) + override val runsBefore = Set(ReifyQuotes.name) override def init(options: List[String]): List[PluginPhase] = this :: Nil From abe2123a5c71f90208c0ff121915ef60ba4b5c7c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 18 Jan 2019 15:45:34 +0100 Subject: [PATCH 14/14] WIP --- .../dotty/tools/dotc/transform/Staging.scala | 99 +++++++++++-------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index d42182b4cc10..3f3622ae3f0a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -102,6 +102,9 @@ class Staging extends MacroTransformWithImplicits { * references to `TypeI` in `expr` are rewired to point to the locally * defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN` * as splices to `embedded`. + * + * `TypeI` is replaced everywere in exept in the contents of a splice that + * is at level 1 (i.e. a nested tree at level zero). */ private def addTags(expr: Tree)(implicit ctx: Context): Tree = { @@ -155,51 +158,61 @@ class Staging extends MacroTransformWithImplicits { } /** Type tree map that does not tag type at level 0 */ - class QuoteTreeTypeMap( - typeMap: Type => Type = IdentityTypeMap, - treeMap: tpd.Tree => tpd.Tree = identity _, - oldOwners: List[Symbol] = Nil, - newOwners: List[Symbol] = Nil, - substFrom: List[Symbol] = Nil, - substTo: List[Symbol] = Nil - )(implicit ctx: Context) extends TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) { self => - - protected var level = 1 // TODO use context to keep track of the level - - override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { - if (level == 0) { - // Keep transforming but do not replace insert the taged types. Types in nested quotes are also not taged. - val (sFrom, sTo) = substFrom.zip(substTo).filterNot(_._2.is(Synthetic)).unzip // TODO Syntetic is probably not enugh to distinguish added types - new TreeTypeMap(typeMap, treeMap, - oldOwners, newOwners, - sFrom, sTo - ).transform(tree) - } - else if (tree.symbol.isSplice) { - level -= 1 - try super.transform(tree) - finally level += 1 - } else if (tree.symbol.isQuote) { - level += 1 - try super.transform(tree) - finally level -= 1 - } - else super.transform(tree) - - } - - protected override def newTreeTypeMap(typeMap: Type => Type, treeMap: tpd.Tree => tpd.Tree, - oldOwners: List[Symbol], newOwners: List[Symbol], - substFrom: List[Symbol], substTo: List[Symbol]) = { - new QuoteTreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) { - level = self.level - } - } - - } +// class QuoteTreeTypeMap( +// typeMap: Type => Type = IdentityTypeMap, +// treeMap: tpd.Tree => tpd.Tree = identity _, +// oldOwners: List[Symbol] = Nil, +// newOwners: List[Symbol] = Nil, +// substFrom: List[Symbol] = Nil, +// substTo: List[Symbol] = Nil +// )(implicit ctx: Context) extends TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) { self => +// +// protected var level = 1 // TODO use context to keep track of the level +// +// override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = { +// if (level == 0) { +// // Keep transforming but do not replace insert the taged types. Types in nested quotes are also not taged. +// val (sFrom, sTo) = substFrom.zip(substTo).filterNot(_._2.is(Synthetic)).unzip // TODO Syntetic is probably not enugh to distinguish added types +// new TreeTypeMap( +// oldOwners = oldOwners, newOwners = newOwners, +// substFrom = sFrom, substTo = sTo +// ).transform(tree) +// } +// else if (tree.symbol.isSplice) { +// level -= 1 +// try { +// val tr = super.transform(tree) +// if (tr.isType) tr +// else { +// // Make sure that the tags are not used inside the splice +// // and that the non aliased type does not escape the splice +// val wtp = tr.tpe.widenTermRefExpr +// val tp = tpMap(wtp) +// val tp1 = if (wtp == tp) tr.tpe else tp +// tr.withType(tree.tpe).asInstance(tp1) +// } +// } finally level += 1 +// } else if (tree.symbol.isQuote) { +// level += 1 +// try super.transform(tree) +// finally level -= 1 +// } +// else super.transform(tree) +// +// } +// +// protected override def newTreeTypeMap(typeMap: Type => Type, treeMap: tpd.Tree => tpd.Tree, +// oldOwners: List[Symbol], newOwners: List[Symbol], +// substFrom: List[Symbol], substTo: List[Symbol]) = { +// new QuoteTreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) { +// level = self.level +// } +// } +// +// } Block(typeDefs ++ explicitTypeDefs, - new QuoteTreeTypeMap( + new /*Quote*/TreeTypeMap( treeMap = trMap, typeMap = tpMap, substFrom = itags.map(_._1.symbol),