From a6e19d4f42b87e4e0ff321605bb682a44ad71c08 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 28 Feb 2018 17:35:51 +0100 Subject: [PATCH 1/8] Fix #4044: Change quote pickled representation --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 11 +- .../tools/dotc/transform/ReifyQuotes.scala | 241 +++++++++++++----- tests/neg/i4044a.scala | 13 + tests/neg/i4044b.scala | 18 ++ tests/run-with-compiler/i4044a.check | 1 + tests/run-with-compiler/i4044a.scala | 17 ++ tests/run-with-compiler/i4044b.check | 5 + tests/run-with-compiler/i4044b.scala | 27 ++ tests/run-with-compiler/i4044c.check | 1 + tests/run-with-compiler/i4044c.scala | 16 ++ tests/run-with-compiler/i4044d.check | 5 + tests/run-with-compiler/i4044d.scala | 25 ++ tests/run-with-compiler/i4044e.check | 1 + tests/run-with-compiler/i4044e.scala | 19 ++ tests/run-with-compiler/i4044f.check | 5 + tests/run-with-compiler/i4044f.scala | 26 ++ tests/run-with-compiler/quote-lambda.scala | 10 + tests/run-with-compiler/quote-nested-1.check | 1 + tests/run-with-compiler/quote-nested-1.scala | 9 + tests/run-with-compiler/quote-nested-2.check | 4 + tests/run-with-compiler/quote-nested-2.scala | 13 + tests/run-with-compiler/quote-nested-3.check | 7 + tests/run-with-compiler/quote-nested-3.scala | 19 ++ tests/run-with-compiler/quote-nested-4.check | 4 + tests/run-with-compiler/quote-nested-4.scala | 13 + tests/run-with-compiler/quote-nested-5.check | 4 + tests/run-with-compiler/quote-nested-5.scala | 16 ++ 27 files changed, 457 insertions(+), 74 deletions(-) create mode 100644 tests/neg/i4044a.scala create mode 100644 tests/neg/i4044b.scala create mode 100644 tests/run-with-compiler/i4044a.check create mode 100644 tests/run-with-compiler/i4044a.scala create mode 100644 tests/run-with-compiler/i4044b.check create mode 100644 tests/run-with-compiler/i4044b.scala create mode 100644 tests/run-with-compiler/i4044c.check create mode 100644 tests/run-with-compiler/i4044c.scala create mode 100644 tests/run-with-compiler/i4044d.check create mode 100644 tests/run-with-compiler/i4044d.scala create mode 100644 tests/run-with-compiler/i4044e.check create mode 100644 tests/run-with-compiler/i4044e.scala create mode 100644 tests/run-with-compiler/i4044f.check create mode 100644 tests/run-with-compiler/i4044f.scala create mode 100644 tests/run-with-compiler/quote-lambda.scala create mode 100644 tests/run-with-compiler/quote-nested-1.check create mode 100644 tests/run-with-compiler/quote-nested-1.scala create mode 100644 tests/run-with-compiler/quote-nested-2.check create mode 100644 tests/run-with-compiler/quote-nested-2.scala create mode 100644 tests/run-with-compiler/quote-nested-3.check create mode 100644 tests/run-with-compiler/quote-nested-3.scala create mode 100644 tests/run-with-compiler/quote-nested-4.check create mode 100644 tests/run-with-compiler/quote-nested-4.scala create mode 100644 tests/run-with-compiler/quote-nested-5.check create mode 100644 tests/run-with-compiler/quote-nested-5.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 7f64f2cfb941..3653be8b04f7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1132,19 +1132,14 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val args = until(end)(readTerm()) val splice = splices(idx) - + val reifiedArgs = args.map(arg => if (arg.isTerm) new TreeExpr(arg) else new TreeType(arg)) if (isType) { - val quotedType = - if (args.isEmpty) splice.asInstanceOf[quoted.Type[_]] - else splice.asInstanceOf[Seq[Any] => quoted.Type[_]](args.map(tree => new TreeType(tree))) + val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs) PickledQuotes.quotedTypeToTree(quotedType) } else { - val quotedExpr = - if (args.isEmpty) splice.asInstanceOf[quoted.Expr[_]] - else splice.asInstanceOf[Seq[Any] => quoted.Expr[_]](args.map(tree => new TreeExpr(tree))) + val quotedExpr = splice.asInstanceOf[Seq[Any] => quoted.Expr[_]](reifiedArgs) PickledQuotes.quotedExprToTree(quotedExpr) } - } // ------ Setting positions ------------------------------------------------ diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index adaec52a2917..76b02f6f19b1 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -5,14 +5,13 @@ import core._ import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._ import Flags._ import ast.Trees._ -import ast.TreeTypeMap +import ast.{TreeTypeMap, untpd} import util.Positions._ import StdNames._ -import ast.untpd import tasty.TreePickler.Hole import MegaPhase.MiniPhase import SymUtils._ -import NameKinds.OuterSelectName +import NameKinds._ import typer.Implicits.SearchFailureType import scala.collection.mutable @@ -22,6 +21,40 @@ import dotty.tools.dotc.core.quoted._ /** 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_~ ...}`. */ class ReifyQuotes extends MacroTransformWithImplicits { import ast.tpd._ @@ -37,38 +70,8 @@ class ReifyQuotes 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] - - /** A stack of entered symbols, to be unwound after scope exit */ - var enteredSyms: List[Symbol] = Nil } - /** Requiring that `paramRefs` consists of a single reference `seq` to a Seq[Any], - * a tree map that replaces each hole with index `n` with `seq(n)`, applied - * to any arguments in the hole. - */ - private def replaceHoles(paramRefs: List[Tree]) = new TreeMap { - val seq :: Nil = paramRefs - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case Hole(n, args) => - val arg = - seq.select(nme.apply).appliedTo(Literal(Constant(n))).ensureConforms(tree.tpe) - if (args.isEmpty) arg - else arg.select(nme.apply).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))) - case _ => - super.transform(tree) - } - } - - /** If `tree` has holes, convert it to a function taking a `Seq` of elements as arguments - * where each hole is replaced by the corresponding sequence element. - */ - private def elimHoles(tree: Tree)(implicit ctx: Context): Tree = - if (tree.existsSubTree(_.isInstanceOf[Hole])) - Lambda( - MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe), - replaceHoles(_).transform(tree)) - else 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 @@ -94,11 +97,14 @@ class ReifyQuotes extends MacroTransformWithImplicits { */ val importedTags = new mutable.LinkedHashMap[TypeRef, Tree]() + /** A stack of entered symbols, to be unwound after scope exit */ + var enteredSyms: List[Symbol] = Nil + /** Assuming importedTags = `Type1 -> tag1, ..., TypeN -> tagN`, the expression * * { type = .unary_~ * ... - * type = .unary.~ + * type = .unary_~ * * } * @@ -106,7 +112,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { * defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN` * as splices to `embedded`. */ - def addTags(expr: Tree)(implicit ctx: Context): Tree = + private def addTags(expr: Tree)(implicit ctx: Context): Tree = if (importedTags.isEmpty) expr else { val itags = importedTags.toList @@ -151,7 +157,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { } /** Issue a "splice outside quote" error unless we ar in the body of an inline method */ - def spliceOutsideQuotes(pos: Position)(implicit ctx: Context) = + def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit = ctx.error(i"splice outside quotes", pos) /** Try to heal phase-inconsistent reference to type `T` using a local type definition. @@ -162,7 +168,6 @@ class ReifyQuotes extends MacroTransformWithImplicits { def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match { case tp: TypeRef => if (level == 0) { - assert(ctx.owner.is(Macro)) None } else { val reqType = defn.QuotedTypeType.appliedTo(tp) @@ -258,46 +263,143 @@ class ReifyQuotes extends MacroTransformWithImplicits { * `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with * core and splices as arguments. */ - private def quotation(body: Tree, quote: Tree)(implicit ctx: Context) = { - val (body1, splices) = nested(isQuote = true).split(body) - if (inSplice) - makeHole(body1, splices, quote.tpe) + private def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val isType = quote.symbol eq defn.typeQuoteMethod + if (level > 0) { + val reifier = nested(isQuote = true) + val body1 = reifier.transform(body) + embedded ++= reifier.embedded + // Keep quotes in quotes as trees to reduce pickled size and have a Expr.show without pickled quotes embedded + if (isType) ref(defn.typeQuoteMethod).appliedToType(body1.tpe.widen) + else ref(defn.quoteMethod).appliedToType(body1.tpe.widen).appliedTo(body1) + } else { - def liftList(list: List[Tree], tpe: Type): Tree = { - list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => - acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) - } - } - val isType = quote.tpe.isRef(defn.QuotedTypeClass) - ref(if (isType) defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr) - .appliedToType(if (isType) body1.tpe else body1.tpe.widen) - .appliedTo( + val (body1, splices) = nested(isQuote = true).split(body) + val meth = + if (isType) ref(defn.Unpickler_unpickleType).appliedToType(body1.tpe) + else ref(defn.Unpickler_unpickleExpr).appliedToType(body1.tpe.widen) + meth.appliedTo( liftList(PickledQuotes.pickleQuote(body1).map(x => Literal(Constant(x))), defn.StringType), - liftList(splices, defn.AnyType)) + liftList(splices, defn.AnyType)).withPos(quote.pos) } - }.withPos(quote.pos) + } - /** If inside a quote, split `body` into a core and a list of embedded quotes + /** 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(body: Tree, splice: Tree)(implicit ctx: Context): Tree = { - if (inQuote) { - val (body1, quotes) = nested(isQuote = false).split(body) - makeHole(body1, quotes, splice.tpe) + private def splice(splice: Select)(implicit ctx: Context): Tree = { + if (level > 1) { + val reifier = nested(isQuote = false) + val body1 = reifier.transform(splice.qualifier) + embedded ++= reifier.embedded + body1.select(splice.name) } - else { + else if (!inQuote && level == 0) { spliceOutsideQuotes(splice.pos) splice } - }.withPos(splice.pos) + else { + val (body1, quotes) = nested(isQuote = false).split(splice.qualifier) + makeHole(body1, quotes, splice.tpe).withPos(splice.pos) + } + } + + /** Transforms the contents of a splice nested + * '{ + * val x = ??? + * val y = ??? + * { ... '{ ... x .. y ... } ... }.unary_~ + * } + * + * { ... '{ ... 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: `lift` + * + * 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 + transformWithLifter(tree)( + (lifted: mutable.ListBuffer[Tree]) => (tree: RefTree) => { + val argTpe = + if (tree.isTerm) defn.QuotedExprType.appliedTo(tree.tpe.widen) + else defn.QuotedTypeType.appliedTo(defn.AnyType) + val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe) + val liftedArg = SyntheticValDef(UniqueName.fresh(tree.name.toTermName).toTermName, selectArg) + i += 1 + embedded += tree + lifted += liftedArg + ref(liftedArg.symbol) + } + ) + } + + val lambdaOwner = ctx.owner.ownersIterator.find(o => levelOf.getOrElse(o, level) == level).get + 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)) + } + + /** Register a reference defined in a quote but used in another quote nested in a splice. + * Returns a lifted version of the reference that needs to be used in its place. + * '{ + * val x = ??? + * { ... '{ ... x ... } ... }.unary_~ + * } + * Lifting 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 `needsLifting` + */ + def lift(tree: RefTree)(implicit ctx: Context): Select = + if (!(level == 0 && outer.enteredSyms.contains(tree.symbol))) outer.lift(tree) + else lifter(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~) + private[this] var lifter: RefTree => Tree = null + + private def transformWithLifter(tree: Tree)( + lifter: mutable.ListBuffer[Tree] => RefTree => Tree)(implicit ctx: Context): Tree = { + val lifted = new mutable.ListBuffer[Tree] + this.lifter = lifter(lifted) + val tree2 = transform(tree) + this.lifter = null + seq(lifted.result(), tree2) + } + + /** Returns true if this tree will be lifted by `makeLambda` */ + private def needsLifting(tree: RefTree)(implicit ctx: Context): Boolean = { + def isInLiftedTree(reifier: Reifier): Boolean = + if (reifier.level == 0) true + else if (reifier.level == 1 && reifier.enteredSyms.contains(tree.symbol)) false + else isInLiftedTree(reifier.outer) + level == 1 && + !tree.symbol.is(Inline) && + levelOf.get(tree.symbol).contains(1) && + !enteredSyms.contains(tree.symbol) && + isInLiftedTree(outer) + } /** 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 = addTags(transform(tree)) - (tree1, embedded.toList.map(elimHoles)) + val tree1 = if (inQuote) addTags(transform(tree)) else makeLambda(tree) + (tree1, embedded.toList) } /** Register `body` as an `embedded` quote or splice @@ -321,8 +423,10 @@ class ReifyQuotes extends MacroTransformWithImplicits { tree match { case Quoted(quotedTree) => quotation(quotedTree, tree) - case Select(body, _) if tree.symbol.isSplice => - splice(body, tree) + case tree: Select if tree.symbol.isSplice => + splice(tree) + case tree: RefTree if needsLifting(tree) => + splice(outer.lift(tree)) case Block(stats, _) => val last = enteredSyms stats.foreach(markDef) @@ -347,7 +451,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { tree case tree: DefDef if tree.symbol.is(Macro) && level == 0 => markDef(tree) - val tree1 = nested(isQuote = true).transform(tree) + nested(isQuote = true).transform(tree) // check macro code as it if appeared in a quoted context cpy.DefDef(tree)(rhs = EmptyTree) case _ => @@ -356,14 +460,19 @@ class ReifyQuotes extends MacroTransformWithImplicits { } } + 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. */ object InlineSplice { def unapply(tree: Tree)(implicit ctx: Context): Option[Select] = { tree match { - case expansion: Select if expansion.symbol.isSplice => - Some(expansion) + case expansion: Select if expansion.symbol.isSplice => Some(expansion) case Block(List(stat), Literal(Constant(()))) => unapply(stat) case Block(Nil, expr) => unapply(expr) case _ => None diff --git a/tests/neg/i4044a.scala b/tests/neg/i4044a.scala new file mode 100644 index 000000000000..daa64fce9fac --- /dev/null +++ b/tests/neg/i4044a.scala @@ -0,0 +1,13 @@ +import scala.quoted._ + +class Test { + + val a = '(1) + '{ + a // error + ~a + '(~a) // error + '( '(~a) ) // error + } + +} diff --git a/tests/neg/i4044b.scala b/tests/neg/i4044b.scala new file mode 100644 index 000000000000..cc44b6fd8a1e --- /dev/null +++ b/tests/neg/i4044b.scala @@ -0,0 +1,18 @@ +import scala.quoted._ + +class Test { + + '{ + + val b = '(3) + + '{ + b // error + ~(b) + ~('(b)) // error + '( '(~b) ) // error + } + + } + +} diff --git a/tests/run-with-compiler/i4044a.check b/tests/run-with-compiler/i4044a.check new file mode 100644 index 000000000000..00750edc07d6 --- /dev/null +++ b/tests/run-with-compiler/i4044a.check @@ -0,0 +1 @@ +3 diff --git a/tests/run-with-compiler/i4044a.scala b/tests/run-with-compiler/i4044a.scala new file mode 100644 index 000000000000..1eaf534cb6cc --- /dev/null +++ b/tests/run-with-compiler/i4044a.scala @@ -0,0 +1,17 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +class Foo { + def foo: Unit = { + val e: Expr[Int] = '(3) + val q = '{ ~( '{ ~e } ) } + println(q.show) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val f = new Foo + f.foo + } +} diff --git a/tests/run-with-compiler/i4044b.check b/tests/run-with-compiler/i4044b.check new file mode 100644 index 000000000000..43c71a999c56 --- /dev/null +++ b/tests/run-with-compiler/i4044b.check @@ -0,0 +1,5 @@ +{ + var x: Int = 4 + x = 3 + x +} diff --git a/tests/run-with-compiler/i4044b.scala b/tests/run-with-compiler/i4044b.scala new file mode 100644 index 000000000000..c4255d7d8d7a --- /dev/null +++ b/tests/run-with-compiler/i4044b.scala @@ -0,0 +1,27 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +sealed abstract class VarRef[T] { + def update(expr: Expr[T]): Expr[Unit] + def expr: Expr[T] +} + +object VarRef { + def apply[T: Type, U](init: Expr[T])(body: VarRef[T] => Expr[U]): Expr[U] = '{ + var x = ~init + ~body( + new VarRef { + def update(e: Expr[T]): Expr[Unit] = '{ x = ~e } + def expr: Expr[T] = '(x) + } + ) + } + +} + +object Test { + def main(args: Array[String]): Unit = { + val q = VarRef(4)(varRef => '{ ~varRef.update(3); ~varRef.expr }) + println(q.show) + } +} diff --git a/tests/run-with-compiler/i4044c.check b/tests/run-with-compiler/i4044c.check new file mode 100644 index 000000000000..7ed6ff82de6b --- /dev/null +++ b/tests/run-with-compiler/i4044c.check @@ -0,0 +1 @@ +5 diff --git a/tests/run-with-compiler/i4044c.scala b/tests/run-with-compiler/i4044c.scala new file mode 100644 index 000000000000..b35fe58851bb --- /dev/null +++ b/tests/run-with-compiler/i4044c.scala @@ -0,0 +1,16 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +class Foo { + def foo: Unit = { + val q = '{ ~( '{ ~( '{ 5 } ) } ) } + println(q.show) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val f = new Foo + f.foo + } +} diff --git a/tests/run-with-compiler/i4044d.check b/tests/run-with-compiler/i4044d.check new file mode 100644 index 000000000000..16281b787396 --- /dev/null +++ b/tests/run-with-compiler/i4044d.check @@ -0,0 +1,5 @@ +evaluating inner quote +{ + val b: Int = 3 + b.+(3) +} diff --git a/tests/run-with-compiler/i4044d.scala b/tests/run-with-compiler/i4044d.scala new file mode 100644 index 000000000000..6653af93d3d5 --- /dev/null +++ b/tests/run-with-compiler/i4044d.scala @@ -0,0 +1,25 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +class Foo { + def foo: Unit = { + val a: Expr[Int] = '(3) + val q: Expr[Int] = '{ + val b = 3 + ~{ + println("evaluating inner quote") + '{ + b + ~a + } + } + } + println(q.show) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val f = new Foo + f.foo + } +} diff --git a/tests/run-with-compiler/i4044e.check b/tests/run-with-compiler/i4044e.check new file mode 100644 index 000000000000..af61855f9eed --- /dev/null +++ b/tests/run-with-compiler/i4044e.check @@ -0,0 +1 @@ +3.+(5).asInstanceOf[Int] diff --git a/tests/run-with-compiler/i4044e.scala b/tests/run-with-compiler/i4044e.scala new file mode 100644 index 000000000000..c3dcc631dc82 --- /dev/null +++ b/tests/run-with-compiler/i4044e.scala @@ -0,0 +1,19 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +class Foo { + def foo: Unit = { + val e: Expr[Int] = '(3) + val f: Expr[Int] = '(5) + val t: Type[Int] = '[Int] + val q = '{ ~( '{ (~e + ~f).asInstanceOf[~t] } ) } + println(q.show) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val f = new Foo + f.foo + } +} diff --git a/tests/run-with-compiler/i4044f.check b/tests/run-with-compiler/i4044f.check new file mode 100644 index 000000000000..409bb72596a0 --- /dev/null +++ b/tests/run-with-compiler/i4044f.check @@ -0,0 +1,5 @@ +{ + val e1: Int = 3 + val f1: Int = 5 + e1.+(2).+(f1) +} diff --git a/tests/run-with-compiler/i4044f.scala b/tests/run-with-compiler/i4044f.scala new file mode 100644 index 000000000000..43372ff23938 --- /dev/null +++ b/tests/run-with-compiler/i4044f.scala @@ -0,0 +1,26 @@ +import scala.quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +class Foo { + def foo: Unit = { + val e: Expr[Int] = '(3) + val f: Expr[Int] = '(5) + def foo(x: Expr[Int], y: Expr[Int]): Expr[Int] = '{ ~x + ~y } + val q = '{ + val e1 = ~e + val f1 = ~f + ~{ + val u = '(2) + foo('{e1 + ~u}, '(f1)) + } + } + println(q.show) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val f = new Foo + f.foo + } +} diff --git a/tests/run-with-compiler/quote-lambda.scala b/tests/run-with-compiler/quote-lambda.scala new file mode 100644 index 000000000000..d09c2e29b7bb --- /dev/null +++ b/tests/run-with-compiler/quote-lambda.scala @@ -0,0 +1,10 @@ +import scala.quoted._ + +object Test { +// def compile(env: Expr[Int]): Expr[Int] = '(3) + + + def main(args: Array[String]): Unit = { + '{ (x: Int) => ~('(x)) } + } +} diff --git a/tests/run-with-compiler/quote-nested-1.check b/tests/run-with-compiler/quote-nested-1.check new file mode 100644 index 000000000000..2378a7f62ea6 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-1.check @@ -0,0 +1 @@ +.'[Int](3) diff --git a/tests/run-with-compiler/quote-nested-1.scala b/tests/run-with-compiler/quote-nested-1.scala new file mode 100644 index 000000000000..410eaa835aae --- /dev/null +++ b/tests/run-with-compiler/quote-nested-1.scala @@ -0,0 +1,9 @@ +import quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +object Test { + def main(args: Array[String]): Unit = { + val q = '{ '(3) } + println(q.show) + } +} diff --git a/tests/run-with-compiler/quote-nested-2.check b/tests/run-with-compiler/quote-nested-2.check new file mode 100644 index 000000000000..5e2472b8ba28 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-2.check @@ -0,0 +1,4 @@ +{ + val a: quoted.Expr[Int] = .'[Int](4) + .'[Int](a.unary_~) +} diff --git a/tests/run-with-compiler/quote-nested-2.scala b/tests/run-with-compiler/quote-nested-2.scala new file mode 100644 index 000000000000..bdc8a01fa8b2 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-2.scala @@ -0,0 +1,13 @@ +import quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +object Test { + def main(args: Array[String]): Unit = { + val q = '{ + val a = '(4) + '(~a) + } + + println(q.show) + } +} diff --git a/tests/run-with-compiler/quote-nested-3.check b/tests/run-with-compiler/quote-nested-3.check new file mode 100644 index 000000000000..4b5686d7ba3f --- /dev/null +++ b/tests/run-with-compiler/quote-nested-3.check @@ -0,0 +1,7 @@ +{ + type T = String + val x: String = "foo" + val z: T = x + () + x: String +} diff --git a/tests/run-with-compiler/quote-nested-3.scala b/tests/run-with-compiler/quote-nested-3.scala new file mode 100644 index 000000000000..15ef33e2e2c9 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-3.scala @@ -0,0 +1,19 @@ +import quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +object Test { + def main(args: Array[String]): Unit = { + + val q = '{ + type T = String + val x = "foo" + ~{ + val y = '(x) + '{ val z: T = ~y } + } + x + } + + println(q.show) + } +} diff --git a/tests/run-with-compiler/quote-nested-4.check b/tests/run-with-compiler/quote-nested-4.check new file mode 100644 index 000000000000..ff8256f4afe7 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-4.check @@ -0,0 +1,4 @@ +{ + val t: quoted.Type[String] = .type_'[String] + t: quoted.Type[String] +} diff --git a/tests/run-with-compiler/quote-nested-4.scala b/tests/run-with-compiler/quote-nested-4.scala new file mode 100644 index 000000000000..49a4a0db587e --- /dev/null +++ b/tests/run-with-compiler/quote-nested-4.scala @@ -0,0 +1,13 @@ +import quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +object Test { + def main(args: Array[String]): Unit = { + val q = '{ + val t = '[String] + t + } + + println(q.show) + } +} diff --git a/tests/run-with-compiler/quote-nested-5.check b/tests/run-with-compiler/quote-nested-5.check new file mode 100644 index 000000000000..5e2472b8ba28 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-5.check @@ -0,0 +1,4 @@ +{ + val a: quoted.Expr[Int] = .'[Int](4) + .'[Int](a.unary_~) +} diff --git a/tests/run-with-compiler/quote-nested-5.scala b/tests/run-with-compiler/quote-nested-5.scala new file mode 100644 index 000000000000..7d1bdd602343 --- /dev/null +++ b/tests/run-with-compiler/quote-nested-5.scala @@ -0,0 +1,16 @@ +import quoted._ +import dotty.tools.dotc.quoted.Toolbox._ + +object Test { + def main(args: Array[String]): Unit = { + val q = '{ + val a = '(4) + ~('{ + '(~a) + }) + + } + + println(q.show) + } +} From b5d0fdfb7c2d5e81d275149533f435fadb18a5cd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 9 Mar 2018 09:19:59 +0100 Subject: [PATCH 2/8] Avoid cuadratic behaviour when lifting references --- .../tools/dotc/transform/ReifyQuotes.scala | 53 ++++++++----------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 76b02f6f19b1..5eab9579d6fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -70,6 +70,21 @@ class ReifyQuotes 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 lifted version of the reference that needs to be used in its place. + * '{ + * val x = ??? + * { ... '{ ... x ... } ... }.unary_~ + * } + * Lifting 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 `needsLifting` + */ + val lifters = new mutable.HashMap[Symbol, RefTree => Tree] } /** The main transformer class @@ -354,44 +369,21 @@ class ReifyQuotes extends MacroTransformWithImplicits { Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth)) } - /** Register a reference defined in a quote but used in another quote nested in a splice. - * Returns a lifted version of the reference that needs to be used in its place. - * '{ - * val x = ??? - * { ... '{ ... x ... } ... }.unary_~ - * } - * Lifting 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 `needsLifting` - */ - def lift(tree: RefTree)(implicit ctx: Context): Select = - if (!(level == 0 && outer.enteredSyms.contains(tree.symbol))) outer.lift(tree) - else lifter(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~) - private[this] var lifter: RefTree => Tree = null - private def transformWithLifter(tree: Tree)( lifter: mutable.ListBuffer[Tree] => RefTree => Tree)(implicit ctx: Context): Tree = { val lifted = new mutable.ListBuffer[Tree] - this.lifter = lifter(lifted) + val lifter2 = lifter(lifted) + outer.enteredSyms.foreach(s => lifters.put(s, lifter2)) val tree2 = transform(tree) - this.lifter = null + lifters --= outer.enteredSyms seq(lifted.result(), tree2) } /** Returns true if this tree will be lifted by `makeLambda` */ private def needsLifting(tree: RefTree)(implicit ctx: Context): Boolean = { - def isInLiftedTree(reifier: Reifier): Boolean = - if (reifier.level == 0) true - else if (reifier.level == 1 && reifier.enteredSyms.contains(tree.symbol)) false - else isInLiftedTree(reifier.outer) - level == 1 && - !tree.symbol.is(Inline) && - levelOf.get(tree.symbol).contains(1) && - !enteredSyms.contains(tree.symbol) && - isInLiftedTree(outer) + // Check phase consistency and presence of lifter + level == 1 && !tree.symbol.is(Inline) && levelOf.get(tree.symbol).contains(1) && + lifters.contains(tree.symbol) } /** Transform `tree` and return the resulting tree and all `embedded` quotes @@ -426,7 +418,8 @@ class ReifyQuotes extends MacroTransformWithImplicits { case tree: Select if tree.symbol.isSplice => splice(tree) case tree: RefTree if needsLifting(tree) => - splice(outer.lift(tree)) + val lift = lifters(tree.symbol) + splice(lift(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) case Block(stats, _) => val last = enteredSyms stats.foreach(markDef) From fc4a648a1c736472fc229934ebb5344e55fc84c8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 9 Mar 2018 10:30:05 +0100 Subject: [PATCH 3/8] Avoid cuadratic behaviour with embedding in levels > 1 --- .../tools/dotc/transform/ReifyQuotes.scala | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 5eab9579d6fa..fd45ec43ca91 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -65,7 +65,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { if (ctx.compilationUnit.containsQuotesOrSplices) super.run protected def newTransformer(implicit ctx: Context): Transformer = - new Reifier(inQuote = false, null, 0, new LevelInfo) + new Reifier(inQuote = false, null, 0, new LevelInfo, new mutable.ListBuffer[Tree]) private class LevelInfo { /** A map from locally defined symbols to the staging levels of their definitions */ @@ -92,21 +92,22 @@ class ReifyQuotes extends MacroTransformWithImplicits { * @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 * @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` */ - private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo) extends ImplicitsTransformer { + private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo, + val embedded: mutable.ListBuffer[Tree]) extends ImplicitsTransformer { import levels._ assert(level >= 0) /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ - def nested(isQuote: Boolean): Reifier = - new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels) + def nested(isQuote: Boolean): Reifier = { + val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new mutable.ListBuffer[Tree] + new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded) + } /** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */ def inSplice = outer != null && !inQuote - /** A list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true`) */ - val embedded = new mutable.ListBuffer[Tree] - /** A map from type ref T to expressions of type `quoted.Type[T]`". * These will be turned into splices using `addTags` */ @@ -281,9 +282,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { private def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { val isType = quote.symbol eq defn.typeQuoteMethod if (level > 0) { - val reifier = nested(isQuote = true) - val body1 = reifier.transform(body) - embedded ++= reifier.embedded + val body1 = nested(isQuote = true).transform(body) // Keep quotes in quotes as trees to reduce pickled size and have a Expr.show without pickled quotes embedded if (isType) ref(defn.typeQuoteMethod).appliedToType(body1.tpe.widen) else ref(defn.quoteMethod).appliedToType(body1.tpe.widen).appliedTo(body1) @@ -305,9 +304,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { */ private def splice(splice: Select)(implicit ctx: Context): Tree = { if (level > 1) { - val reifier = nested(isQuote = false) - val body1 = reifier.transform(splice.qualifier) - embedded ++= reifier.embedded + val body1 = nested(isQuote = false).transform(splice.qualifier) body1.select(splice.name) } else if (!inQuote && level == 0) { From 0e8f855a63dac730fae33bdf5511c405b7afc557 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 12 Mar 2018 13:31:35 +0100 Subject: [PATCH 4/8] Fix comment --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index fd45ec43ca91..06e7e665ce61 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -283,7 +283,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { val isType = quote.symbol eq defn.typeQuoteMethod if (level > 0) { val body1 = nested(isQuote = true).transform(body) - // Keep quotes in quotes as trees to reduce pickled size and have a Expr.show without pickled quotes embedded + // Keep quotes as trees to reduce pickled size and have a Expr.show without pickled quotes if (isType) ref(defn.typeQuoteMethod).appliedToType(body1.tpe.widen) else ref(defn.quoteMethod).appliedToType(body1.tpe.widen).appliedTo(body1) } @@ -470,4 +470,4 @@ class ReifyQuotes extends MacroTransformWithImplicits { } } } -} \ No newline at end of file +} From 782f57a2b6e0c27ec4fe70d003477215c744434a Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Thu, 15 Mar 2018 17:02:35 +0100 Subject: [PATCH 5/8] Update quote-lambda.scala --- tests/run-with-compiler/quote-lambda.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/run-with-compiler/quote-lambda.scala b/tests/run-with-compiler/quote-lambda.scala index d09c2e29b7bb..91a871ab1bc8 100644 --- a/tests/run-with-compiler/quote-lambda.scala +++ b/tests/run-with-compiler/quote-lambda.scala @@ -1,9 +1,6 @@ import scala.quoted._ object Test { -// def compile(env: Expr[Int]): Expr[Int] = '(3) - - def main(args: Array[String]): Unit = { '{ (x: Int) => ~('(x)) } } From da6d379e396d8d8a97886ba49ebf572867792b08 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 18:57:01 +0100 Subject: [PATCH 6/8] Improve doc --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 06e7e665ce61..ba1957cb01d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -317,13 +317,14 @@ class ReifyQuotes extends MacroTransformWithImplicits { } } - /** Transforms the contents of a splice nested + /** 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]) => { From 9a911ba8121ef3d447d166b0a373b7fbaff625dc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 19:02:39 +0100 Subject: [PATCH 7/8] Add back assert --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index ba1957cb01d7..199eaa3a6369 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -184,6 +184,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match { case tp: TypeRef => if (level == 0) { + assert(ctx.owner.is(Macro)) None } else { val reqType = defn.QuotedTypeType.appliedTo(tp) From b74c7237f097a9a4c6e4a77f0d4f27673584c6ae Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 15 Mar 2018 20:11:42 +0100 Subject: [PATCH 8/8] Modify addert to handle nested definitions in the macro --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 199eaa3a6369..baf59419cc4a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -184,7 +184,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match { case tp: TypeRef => if (level == 0) { - assert(ctx.owner.is(Macro)) + assert(ctx.owner.ownersIterator.exists(_.is(Macro))) None } else { val reqType = defn.QuotedTypeType.appliedTo(tp)