From 85e00d5b1f7db5966c9cd33dc28b5338cb66d092 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 21 Jan 2019 14:59:03 +0100 Subject: [PATCH 01/12] Extract quote reification from Staging Split current `Staging` into `Staging` and `ReifyQuotes` - `Staging` - Just before `Pickler` - Checks PCP - Heals phase consistency by replacing `T` with `~implicitly[Type[T]]({{t}})` (actually `~t`) - Expand macros - Ycheck that PCP holds (without healing) until `ReifyQuotes` - `ReifyQuotes` - Aliases all type splices by replacing `~t` by `T$1` and inserting `type T$1 = ~t` at the start of the quoted block. - Transform `'{ ... ~(... ) ... }` into `unpickle('{ ... Hole(...) ... }, List(args => ....))` - `TreeMapWithStages` a new `TreeMap` that contains the logic to track quotation levels while mapping the tree. - Moved logic from `MacroTransformWithImplicits` to `TreeMapWithImplicits` to be able to tranform a tree without the need to be in a macro transform. This will be useful to integrate the PCP checks and macro expansion in `Typer`. --- compiler/src/dotty/tools/dotc/Compiler.scala | 3 +- .../tools/dotc/ast/TreeMapWithImplicits.scala | 107 +++ compiler/src/dotty/tools/dotc/ast/Trees.scala | 3 + .../src/dotty/tools/dotc/core/Contexts.scala | 1 + .../src/dotty/tools/dotc/core/Phases.scala | 3 + .../dotty/tools/dotc/core/quoted/Quoted.scala | 2 +- .../tools/dotc/core/quoted/Spliced.scala | 19 + .../tools/dotc/printing/RefinedPrinter.scala | 13 +- .../tools/dotc/tastyreflect/TreeOpsImpl.scala | 2 +- .../MacroTransformWithImplicits.scala | 87 -- .../tools/dotc/transform/ReifyQuotes.scala | 426 +++++++++ .../dotty/tools/dotc/transform/Staging.scala | 814 +++++------------- .../dotc/transform/TreeMapWithStages.scala | 145 ++++ .../src-bootstrapped/scala/StagedTuple.scala | 2 +- .../scala/tasty/reflect/utils/TreeUtils.scala | 18 +- .../scala/tasty/reflect/utils/TreeUtils.scala | 4 +- tests/neg/quote-0.scala | 4 +- tests/plugins/neg/divideZero/plugin_1.scala | 4 +- tests/pos/i4023c/Macro_1.scala | 4 +- tests/pos/i4774e.scala | 10 + tests/pos/i4774f.scala | 10 + tests/pos/quote-bind-T.scala | 10 + tests/pos/quote-liftable-list-2.scala | 13 + tests/pos/quote-liftable-list.scala | 9 + .../staged-streams_1.scala | 2 +- tests/run-with-compiler/i3823-b.scala | 2 +- tests/run-with-compiler/i3823.scala | 2 +- tests/run-with-compiler/i3947.scala | 2 +- tests/run-with-compiler/i3947b.scala | 2 +- tests/run-with-compiler/i3947b2.scala | 2 +- tests/run-with-compiler/i3947b3.scala | 2 +- tests/run-with-compiler/i3947c.scala | 2 +- tests/run-with-compiler/i3947d.scala | 2 +- tests/run-with-compiler/i3947d2.scala | 2 +- tests/run-with-compiler/i3947e.scala | 2 +- tests/run-with-compiler/i3947f.scala | 2 +- tests/run-with-compiler/i3947g.scala | 2 +- tests/run-with-compiler/i3947i.scala | 2 +- tests/run-with-compiler/i3947j.scala | 2 +- tests/run-with-compiler/i4044b.scala | 2 +- tests/run-with-compiler/i4350.check | 4 +- tests/run-with-compiler/i4350.scala | 2 +- tests/run-with-compiler/i5247.check | 4 +- tests/run-with-compiler/quote-lib.scala | 6 +- tests/run-with-compiler/quote-type-tags.scala | 2 +- .../shonan-hmm/Lifters.scala | 2 +- .../run-with-compiler/shonan-hmm/MVmult.scala | 2 +- .../shonan-hmm/UnrolledExpr.scala | 2 +- .../tasty-unsafe-let/quoted_1.scala | 2 +- .../gestalt-optional-staging/Macro_1.scala | 4 +- 50 files changed, 1049 insertions(+), 726 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala create mode 100644 compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala delete mode 100644 compiler/src/dotty/tools/dotc/transform/MacroTransformWithImplicits.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala create mode 100644 tests/pos/i4774e.scala create mode 100644 tests/pos/i4774f.scala create mode 100644 tests/pos/quote-bind-T.scala create mode 100644 tests/pos/quote-liftable-list-2.scala create mode 100644 tests/pos/quote-liftable-list.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 15340cbca8db..fca79f882644 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -45,8 +45,9 @@ class Compiler { /** Phases dealing with TASTY tree pickling and unpickling */ protected def picklerPhases: List[List[Phase]] = + List(new Staging) :: // Check PCP, heal quoted types and expand macros List(new Pickler) :: // Generate TASTY info - List(new Staging) :: // Expand macros and turn quoted trees into explicit run-time data structures + 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/ast/TreeMapWithImplicits.scala b/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala new file mode 100644 index 000000000000..4d69a1538f1c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala @@ -0,0 +1,107 @@ +package dotty.tools.dotc +package ast + +import core._ +import ast.Trees._ +import Contexts._ +import Symbols._ +import annotation.tailrec + +/** A TreeMap that maintains the necessary infrastructore to support + * contxtual implicit searches (type-scope implicits are supported anyway). + */ +class TreeMapWithImplicits extends ast.tpd.TreeMap { + import ast.tpd._ + + protected def localCtx(tree: Tree)(implicit ctx: Context): FreshContext = { + val sym = tree.symbol + val owner = if (sym is Flags.PackageVal) sym.moduleClass else sym + ctx.fresh.setTree(tree).setOwner(owner) + } + + def transformSelf(vd: ValDef)(implicit ctx: Context): ValDef = + cpy.ValDef(vd)(tpt = transform(vd.tpt)) + + /** Transform statements, while maintaining import contexts and expression contexts + * in the same way as Typer does. The code addresses additional concerns: + * - be tail-recursive where possible + * - don't re-allocate trees where nothing has changed + */ + def transformStats(stats: List[Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { + + @tailrec def traverse(curStats: List[Tree])(implicit ctx: Context): List[Tree] = { + + def recur(stats: List[Tree], changed: Tree, rest: List[Tree])(implicit ctx: Context): List[Tree] = { + if (stats eq curStats) { + val rest1 = transformStats(rest, exprOwner) + changed match { + case Thicket(trees) => trees ::: rest1 + case tree => tree :: rest1 + } + } + else stats.head :: recur(stats.tail, changed, rest) + } + + curStats match { + case stat :: rest => + val statCtx = stat match { + case stat: DefTree => ctx + case _ => ctx.exprContext(stat, exprOwner) + } + val restCtx = stat match { + case stat: Import => ctx.importContext(stat, stat.symbol) + case _ => ctx + } + val stat1 = transform(stat)(statCtx) + if (stat1 ne stat) recur(stats, stat1, rest)(restCtx) + else traverse(rest)(restCtx) + case nil => + stats + } + } + traverse(stats) + } + + private def nestedScopeCtx(defs: List[Tree])(implicit ctx: Context): Context = { + val nestedCtx = ctx.fresh.setNewScope + defs foreach { + case d: DefTree => nestedCtx.enter(d.symbol) + case _ => + } + nestedCtx + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + try tree match { + case tree: Block => + super.transform(tree)(nestedScopeCtx(tree.stats)) + case tree: DefDef => + implicit val ctx1 = localCtx(tree)(ctx) + cpy.DefDef(tree)( + tree.name, + transformSub(tree.tparams), + tree.vparamss mapConserve (transformSub(_)), + transform(tree.tpt), + transform(tree.rhs)(nestedScopeCtx(tree.vparamss.flatten))) + case EmptyValDef => + tree + case _: PackageDef | _: MemberDef => + super.transform(tree)(localCtx(tree)) + case impl @ Template(constr, parents, self, _) => + cpy.Template(tree)( + transformSub(constr), + transform(parents)(ctx.superCallContext), + Nil, + transformSelf(self), + transformStats(impl.body, tree.symbol)) + case _ => + super.transform(tree) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.sourcePos) + tree + } + } + +} diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index d5cac688b835..ba085345aac3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -15,6 +15,7 @@ import annotation.internal.sharable import annotation.unchecked.uncheckedVariance import annotation.constructorOnly import Decorators._ +import dotty.tools.dotc.core.tasty.TreePickler.Hole object Trees { @@ -1435,6 +1436,8 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) + case Hole(_, args) => + this(x, args) case _ => foldMoreCases(x, tree) } diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e5424a4b9998..842132270615 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -473,6 +473,7 @@ object Contexts { def typerPhase: Phase = base.typerPhase def sbtExtractDependenciesPhase: Phase = base.sbtExtractDependenciesPhase def picklerPhase: Phase = base.picklerPhase + def reifyQuotesPhase: Phase = base.reifyQuotesPhase def refchecksPhase: Phase = base.refchecksPhase def patmatPhase: Phase = base.patmatPhase def elimRepeatedPhase: Phase = base.elimRepeatedPhase diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 8de12f23b8f8..142a2e659fc2 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -219,6 +219,7 @@ object Phases { private[this] var myTyperPhase: Phase = _ private[this] var mySbtExtractDependenciesPhase: Phase = _ private[this] var myPicklerPhase: Phase = _ + private[this] var myReifyQuotesPhase: Phase = _ private[this] var myCollectNullableFieldsPhase: Phase = _ private[this] var myRefChecksPhase: Phase = _ private[this] var myPatmatPhase: Phase = _ @@ -235,6 +236,7 @@ object Phases { final def typerPhase: Phase = myTyperPhase final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase + final def reifyQuotesPhase: Phase = myReifyQuotesPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase final def refchecksPhase: Phase = myRefChecksPhase final def patmatPhase: Phase = myPatmatPhase @@ -254,6 +256,7 @@ object Phases { myTyperPhase = phaseOfClass(classOf[FrontEnd]) mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) + myReifyQuotesPhase = phaseOfClass(classOf[ReifyQuotes]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) myRefChecksPhase = phaseOfClass(classOf[RefChecks]) myElimRepeatedPhase = phaseOfClass(classOf[ElimRepeated]) diff --git a/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala b/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala index db2b6ecf50ff..e2aa1b07f604 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala @@ -10,7 +10,7 @@ import dotty.tools.dotc.transform.SymUtils._ object Quoted { /** Extracts the content of a quoted tree. - * The result can be the contents of a term ot type quote, which + * The result can be the contents of a term or type quote, which * will return a term or type tree respectively. */ def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[tpd.Tree] = tree match { diff --git a/compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala b/compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala new file mode 100644 index 000000000000..95bbfd07bae7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala @@ -0,0 +1,19 @@ +package dotty.tools.dotc.core.quoted + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Types.Type +import dotty.tools.dotc.transform.SymUtils._ + +/** Extractors for splices */ +object Spliced { + + /** Extracts the content of a spliced tree. + * The result can be the contents of a term or type splice, which + * will return a term or type tree respectively. + */ + def unapply(tree: tpd.Select)(implicit ctx: Context): Option[tpd.Tree] = + if (tree.symbol.isSplice) Some(tree.qualifier) else None + +} diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index dac051cbd8af..23b5cb9a8041 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -251,8 +251,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { protected def blockText[T >: Untyped](trees: List[Tree[T]]): Text = ("{" ~ toText(trees, "\n") ~ "}").close - protected def typeApplyText[T >: Untyped](tree: TypeApply[T]): Text = - toTextLocal(tree.fun) ~ "[" ~ toTextGlobal(tree.args, ", ") ~ "]" + protected def typeApplyText[T >: Untyped](tree: TypeApply[T]): Text = { + if (tree.fun.hasType && tree.fun.symbol == defn.QuotedType_apply) + keywordStr("'[") ~ toTextGlobal(tree.args, ", ") ~ keywordStr("]") + else + toTextLocal(tree.fun) ~ "[" ~ toTextGlobal(tree.args, ", ") ~ "]" + } protected def toTextCore[T >: Untyped](tree: Tree[T]): Text = { import untpd.{modsDeco => _, _} @@ -320,7 +324,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (name.isTypeName) typeText(txt) else txt case tree @ Select(qual, name) => - if (tree.hasType && tree.symbol == defn.QuotedExpr_~ || tree.symbol == defn.QuotedType_~) keywordStr("~(") ~ toTextLocal(qual) ~ keywordStr(")") + if (tree.hasType && tree.symbol == defn.QuotedExpr_~) keywordStr("~(") ~ toTextLocal(qual) ~ keywordStr(")") + else if (tree.hasType && tree.symbol == defn.QuotedType_~) typeText("~(") ~ toTextLocal(qual) ~ typeText(")") else if (qual.isType) toTextLocal(qual) ~ "#" ~ typeText(toText(name)) else toTextLocal(qual) ~ ("." ~ nameIdText(tree) provided name != nme.CONSTRUCTOR) case tree: This => @@ -334,8 +339,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } else if (fun.hasType && fun.symbol == defn.QuotedExpr_apply) keywordStr("'{") ~ toTextGlobal(args, ", ") ~ keywordStr("}") - else if (fun.hasType && fun.symbol == defn.QuotedType_apply) - keywordStr("'[") ~ toTextGlobal(args, ", ") ~ keywordStr("]") else toTextLocal(fun) ~ "(" ~ toTextGlobal(args, ", ") ~ ")" case tree: TypeApply => diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala index fe62efc28e17..3434c43da8a3 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/TreeOpsImpl.scala @@ -777,7 +777,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/MacroTransformWithImplicits.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransformWithImplicits.scala deleted file mode 100644 index d00c9027435b..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransformWithImplicits.scala +++ /dev/null @@ -1,87 +0,0 @@ -package dotty.tools.dotc -package transform - -import core._ -import ast.Trees._ -import Contexts._ -import Symbols._ -import annotation.tailrec - -/** A Macrotransform that maintains the necessary infrastructore to support - * contxtual implicit searches (type-scope implicits are supported anyway). - */ -abstract class MacroTransformWithImplicits extends MacroTransform { - import ast.tpd._ - - override def allowsImplicitSearch: Boolean = true - - class ImplicitsTransformer extends Transformer { - - /** Transform statements, while maintaining import contexts and expression contexts - * in the same way as Typer does. The code addresses additional concerns: - * - be tail-recursive where possible - * - don't re-allocate trees where nothing has changed - */ - override def transformStats(stats: List[Tree], exprOwner: Symbol)(implicit ctx: Context): List[Tree] = { - - @tailrec def traverse(curStats: List[Tree])(implicit ctx: Context): List[Tree] = { - - def recur(stats: List[Tree], changed: Tree, rest: List[Tree])(implicit ctx: Context): List[Tree] = { - if (stats eq curStats) { - val rest1 = transformStats(rest, exprOwner) - changed match { - case Thicket(trees) => trees ::: rest1 - case tree => tree :: rest1 - } - } - else stats.head :: recur(stats.tail, changed, rest) - } - - curStats match { - case stat :: rest => - val statCtx = stat match { - case stat: DefTree => ctx - case _ => ctx.exprContext(stat, exprOwner) - } - val restCtx = stat match { - case stat: Import => ctx.importContext(stat, stat.symbol) - case _ => ctx - } - val stat1 = transform(stat)(statCtx) - if (stat1 ne stat) recur(stats, stat1, rest)(restCtx) - else traverse(rest)(restCtx) - case nil => - stats - } - } - traverse(stats) - } - - private def nestedScopeCtx(defs: List[Tree])(implicit ctx: Context): Context = { - val nestedCtx = ctx.fresh.setNewScope - defs foreach { - case d: DefTree => nestedCtx.enter(d.symbol) - case _ => - } - nestedCtx - } - - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - def localCtx = ctx.withOwner(tree.symbol) - tree match { - case tree: Block => - super.transform(tree)(nestedScopeCtx(tree.stats)) - case tree: DefDef => - implicit val ctx = localCtx - cpy.DefDef(tree)( - tree.name, - transformSub(tree.tparams), - tree.vparamss mapConserve (transformSub(_)), - transform(tree.tpt), - transform(tree.rhs)(nestedScopeCtx(tree.vparamss.flatten))) - case _ => - super.transform(tree) - } - } - } -} \ No newline at end of file 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..0d274ffe7e01 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -0,0 +1,426 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import Constants._ +import ast.Trees._ +import ast.{TreeTypeMap, untpd} +import util.Spans._ +import util.SourcePosition +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.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.util.SourcePosition + +import scala.annotation.constructorOnly + + +/** 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 MacroTransform { + import ReifyQuotes._ + import tpd._ + + 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) + case _ => + } + } + + override def run(implicit ctx: Context): Unit = + if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext) + + protected def newTransformer(implicit ctx: Context): Transformer = new Transformer { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = + new QuoteReifier(null, new mutable.HashMap[Symbol, Tree => Tree], new Embedded, ctx.owner)(ctx).transform(tree) + } + + /** The main transformer class + * @param outer the next outer reifier, null is this is the topmost transformer + * @param embedded a list of embedded quotes (if in a splice) or splices (if in a quote) + * @param owner the owner in the destination lifted lambda + * @param capturers 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`. + */ + private class QuoteReifier(outer: QuoteReifier, capturers: mutable.HashMap[Symbol, Tree => Tree], + val embedded: Embedded, val owner: Symbol)(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { self => + + import TreeMapWithStages._ + + /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ + def nested(isQuote: Boolean)(implicit ctx: Context): QuoteReifier = { + val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded + new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) + } + + /** Assuming contains types `.unary_~, ..., .unary_~`, the expression + * + * { type = .unary_~ + * ... + * type = .unary_~ + * + * } + * + * 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. + */ + private def addTags(expr: Tree)(implicit ctx: Context): Tree = { + + def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { + val splicedTree = tpd.ref(spliced) + val rhs = transform(splicedTree.select(tpnme.UNARY_~)) + val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs) + val local = ctx.newSymbol( + owner = ctx.owner, + name = UniqueName.fresh((splicedTree.symbol.name.toString + "$" + nme.UNARY_~).toTermName).toTypeName, + flags = Synthetic, + info = TypeAlias(splicedTree.tpe.select(tpnme.UNARY_~)), + coord = spliced.termSymbol.coord).asType + + ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) + } + + val tagDefCache = new mutable.LinkedHashMap[Symbol, TypeDef]() + + def typeTagMap = new TypeMap() { + def apply(tp: Type): Type = tp match { + case tp: TypeRef if tp.symbol.isSplice => + tp.prefix match { + case prefix: TermRef => + val tagDef = tagDefCache.getOrElseUpdate(prefix.symbol, mkTagSymbolAndAssignType(prefix)) + tagDef.symbol.typeRef + } + case _ => + mapOver(tp) + } + } + + val tagedTree = new TreeTypeMap(typeMap = typeTagMap).apply(expr) + + if (tagDefCache.isEmpty) expr + else Block(tagDefCache.valuesIterator.toList, tagedTree) + } + + /** 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. + */ + override protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val isType = quote.symbol eq defn.QuotedType_apply + assert(!body.symbol.isSplice) + if (level > 0) { + val body1 = nested(isQuote = true).transform(body)(quoteContext) + super.quotation(body1, quote) + } + 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).splitQuote(body)(quoteContext) + if (level == 0 && !ctx.inInlineMethod) { + val body2 = + if (body1.isType) body1 + else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1) + pickledQuote(body2, splices, body.tpe, isType).withSpan(quote.span) + } + 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(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 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. + */ + protected def splice(splice: Select)(implicit ctx: Context): Tree = { + if (level > 1) { + val body1 = nested(isQuote = false).transform(splice.qualifier)(spliceContext) + body1.select(splice.name) + } + else { + assert(level == 1, "unexpected top splice outside quote") + val (body1, quotes) = nested(isQuote = false).splitSplice(splice.qualifier)(spliceContext) + val tpe = outer.embedded.getHoleType(splice) + val hole = makeHole(body1, quotes, tpe).withSpan(splice.span) + // 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).withSpan(splice.span) + } + } + + /** 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 + } + val refSym = captured.getOrElseUpdate(tree.symbol, newCapture).symbol + ref(refSym).withSpan(tree.span) + } + } + ) + } + /* 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.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.localSymbols.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) + + val tree2 = transform(tree) + capturers --= outer.localSymbols + + 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(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 splitQuote(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { + val tree1 = addTags(transform(tree)) + (tree1, embedded.getTrees) + } + + private def splitSplice(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { + val tree1 = 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"Reifier.transform $tree at $level", show = true) { + tree match { + case TypeApply(Select(spliceTree @ Spliced(_), _), tp) if tree.symbol == defn.Any_asInstanceOf => + // Splice term which should be in the form `x.unary_~.asInstanceOf[T]` where T is an artefact of + // typer to allow pickling/unpickling phase consistent types + splice(spliceTree) + + case tree: TypeTree if tree.tpe.typeSymbol.isSplice => + val splicedType = tree.tpe.stripTypeVar.asInstanceOf[TypeRef].prefix.termSymbol + splice(ref(splicedType).select(tpnme.UNARY_~).withSpan(tree.span)) + + 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 tree: DefDef if tree.symbol.is(Macro) && level == 0 => + // Shrink size of the tree. The methods have already been inlined. + // TODO move to FirstTransform to trigger even without quotes + cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + + case _ => + super.transform(tree) + } + } + + 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) + } + } + } +} + + +object ReifyQuotes { + import tpd._ + + 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 72c32ae68fde..b3b0d3dc5c1c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -1,67 +1,39 @@ package dotty.tools.dotc package transform -import core._ -import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._ -import ast.Trees._ -import ast.{TreeTypeMap, untpd} -import util.Spans._ -import util.SourcePosition -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.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.quoted._ +import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.tasty.TreePickler.Hole +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.Spans._ +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Implicits.SearchFailureType import dotty.tools.dotc.typer.Inliner + +import scala.collection.mutable import dotty.tools.dotc.util.SourcePosition +import scala.annotation.constructorOnly -/** 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, heals types and expand macros. * + * 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. */ -class Staging extends MacroTransformWithImplicits { +class Staging extends MacroTransform { import tpd._ import Staging._ @@ -77,373 +49,97 @@ class Staging extends MacroTransformWithImplicits { override def phaseName: String = Staging.name + override def allowsImplicitSearch: Boolean = true + 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 _ => + if (ctx.phase <= ctx.reifyQuotesPhase) { + // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald + tree match { + case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => + val checker = new PCPCheckAndHeal(freshStagingContext) { + override protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = tree + + override protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = { + def symStr = + if (!tp.isInstanceOf[ThisType]) sym.show + else if (sym.is(ModuleClass)) sym.sourceModule.show + else i"${sym.name}.this" + + val errMsg = s"\nin ${ctx.owner.fullName}" + assert(false, + em"""access to $symStr from wrong staging level: + | - the definition is at level ${levelOf(sym).getOrElse(0)}, + | - but the access is at level $level.$errMsg""") + + None + } + } + checker.transform(tree) + case _ => + } } } override def run(implicit ctx: Context): Unit = - if (ctx.compilationUnit.needsStaging) super.run + if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext) - 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] + protected def newTransformer(implicit ctx: Context): Transformer = new Transformer { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = + new PCPCheckAndHeal(ctx).transform(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) - } + private class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { - /** 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. - */ - val importedTags = new mutable.LinkedHashMap[TypeRef, Tree]() - - /** A map from type ref T to expressions of type `quoted.Type[T]`" like `importedTags` - * These will be turned into splices using `addTags` and represent types spliced - * explicitly. - */ - val explicitTags = new mutable.LinkedHashSet[TypeRef]() - - /** 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_~ - * - * } - * - * 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`. - */ - private def addTags(expr: Tree)(implicit ctx: Context): Tree = { - - def mkTagSymbolAndAssignType(typeRef: TypeRef, tag: Tree): Tree = { - val rhs = transform(tag.select(tpnme.UNARY_~)) - val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs) - - val local = ctx.newSymbol( - owner = ctx.owner, - name = UniqueName.fresh("T".toTermName).toTypeName, - flags = Synthetic, - info = TypeAlias(tag.tpe.select(tpnme.UNARY_~)), - coord = typeRef.prefix.termSymbol.coord).asType - - ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) - } - - if (importedTags.isEmpty && explicitTags.isEmpty) expr - else { - val itags = importedTags.toList - // The tree of the tag for each tag comes from implicit search in `tryHeal` - val typeDefs = for ((tref, tag) <- itags) yield { - mkTagSymbolAndAssignType(tref, tag) - } - 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) - mkTagSymbolAndAssignType(tref, tag) - } - val tagsExplicitTypeDefsPairs = explicitTags.zip(explicitTypeDefs) - explicitTags.clear() - - // Maps type splices to type references of tags e.g., ~t -> some type T$1 - val map: Map[Type, Type] = { - 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)) - } - - Block(typeDefs ++ explicitTypeDefs, - new TreeTypeMap( - typeMap = tMap, - substFrom = itags.map(_._1.symbol), - substTo = typeDefs.map(_.symbol) - ).apply(expr)) - } - } - - /** 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 _ => - } - - /** Does the level of `sym` match the current level? - * An exception is made for inline vals in macros. These are also OK if their level - * is one higher than the current level, because on execution such values - * are constant expression trees and we can pull out the constant from the tree. - */ - def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf.get(sym) match { - case Some(l) => - l == level || - level == -1 && ( - sym == defn.TastyReflection_macroContext || - // here we assume that Splicer.canBeSpliced was true before going to level -1, - // this implies that all non-inline arguments are quoted and that the following two cases are checked - // on inline parameters or type parameters. - sym.is(Param) || - sym.isClass // reference to this in inline methods - ) - case None => - !sym.is(Param) || levelOK(sym.owner) - } - - /** Try to heal phase-inconsistent reference to type `T` using a local type definition. - * @return None if successful - * @return Some(msg) if unsuccessful where `msg` is a potentially empty error message - * to be added to the "inconsistent phase" message. - */ - def tryHeal(tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[String] = tp match { - case tp: TypeRef => - if (level == -1) { - assert(ctx.inInlineMethod) - None - } else { - val reqType = defn.QuotedTypeType.appliedTo(tp) - val tag = ctx.typer.inferImplicitArg(reqType, pos.span) - tag.tpe match { - case fail: SearchFailureType => - Some(i""" - | - | The access would be accepted with the right type tag, but - | ${ctx.typer.missingArgMsg(tag, reqType, "")}""") - case _ => - importedTags(tp) = nested(isQuote = false).transform(tag) - None - } - } - case _ => - Some("") - } - - /** Check reference to `sym` for phase consistency, where `tp` is the underlying type - * by which we refer to `sym`. - */ - def check(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Unit = { - val isThis = tp.isInstanceOf[ThisType] - def symStr = - if (!isThis) sym.show - else if (sym.is(ModuleClass)) sym.sourceModule.show - 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: - | - the definition is at level ${levelOf.getOrElse(sym, 0)}, - | - but the access is at level $level.$errMsg""", pos) - } - - /** Check all named types and this-types in a given type for phase consistency. */ - def checkType(pos: SourcePosition)(implicit ctx: Context): TypeAccumulator[Unit] = new TypeAccumulator[Unit] { - def apply(acc: Unit, tp: Type): Unit = reporting.trace(i"check type level $tp at $level") { - tp match { - case tp: TypeRef if tp.symbol.isSplice => - if (inQuote) { - explicitTags += tp - outer.checkType(pos).foldOver(acc, tp) + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + reporting.trace(i"PCPTransformer.transform $tree at $level", show = true) { + tree match { + case tree: DefDef if tree.symbol.is(Macro) => + if (level > 0) { + super.transform(tree) // Ignore output, only check PCP + EmptyTree // Already inlined } - else { - if (tp.isTerm) ctx.error(i"splice outside quotes", pos) - tp + else if (enclosingInlineds.nonEmpty) { + EmptyTree // Already checked at definition site and already inlined + } + else tree.rhs match { + case InlineSplice(_) => + super.transform(tree) // Ignore output, only check PCP + tree + case _ => + ctx.error( + """Malformed macro. + | + |Expected the ~ to be at the top of the RHS: + | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) + | + | * The contents of the splice must call a static method + | * All arguments must be quoted or inline + """.stripMargin, tree.rhs.sourcePos) + tree } - case tp: NamedType => - check(tp.symbol, tp, tp.symbol.sourcePos) - if (!tp.symbol.is(Param)) - foldOver(acc, tp) - case tp: ThisType => - check(tp.cls, tp, tp.cls.sourcePos) - foldOver(acc, tp) case _ => - foldOver(acc, tp) + checkLevel(super.transform(tree)) } } } - /** If `tree` refers to a locally defined symbol (either directly, or in a pickled type), - * check that its staging level matches the current level. References to types - * that are phase-incorrect can still be healed as follows: - * - * If `T` is a reference to a type at the wrong level, heal it by setting things up - * so that we later add a type definition - * - * type T' = ~quoted.Type[T] - * - * to the quoted text and rename T to T' in it. This is done later in `reify` via - * `addTags`. `checkLevel` itself only records what needs to be done in the - * `typeTagOfRef` field of the current `Splice` structure. - */ - private def checkLevel(tree: Tree)(implicit ctx: Context): Tree = { - tree match { - case (_: Ident) | (_: This) => - check(tree.symbol, tree.tpe, tree.sourcePos) - case (_: UnApply) | (_: TypeTree) => - checkType(tree.sourcePos).apply((), tree.tpe) - case Select(qual, OuterSelectName(_, levels)) => - checkType(tree.sourcePos).apply((), tree.tpe.widen) - case _: Bind => - checkType(tree.sourcePos).apply((), tree.symbol.info) - case _: Template => - checkType(tree.sourcePos).apply((), tree.symbol.owner.asClass.givenSelfType) - case _ => - } - tree + /** Transform quoted trees while maintaining phase correctness */ + override protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + val body1 = transform(body)(quoteContext) + super.quotation(body1, quote) } - /** 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. + /** Transform splice + * - If inside a quote, transform transform the contents of the splice. + * - If inside inlined code, expand the macro code. + * - If inside of a macro definition, check the validity of the macro. */ - 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.sourcePos), Nil, body1) - pickledQuote(body2, splices, body.tpe, isType).withSpan(quote.span) - } - 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(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).withSpan(splice.span) - // 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).withSpan(splice.span) + protected def splice(splice: Select)(implicit ctx: Context): Tree = { + if (level >= 1) { + val body1 = transform(splice.qualifier)(spliceContext) + val splice1 = cpy.Select(splice)(body1, splice.name) + if (splice1.isType) splice1 + else addSpliceCast(splice1) } else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call val spliceCtx = ctx.outer // drop the last `inlineContext` @@ -456,234 +152,180 @@ 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 + transform(splice.qualifier)(spliceContext) // Just check PCP splice } else { // level 0 inside an inline definition ctx.error( - "Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.".stripMargin, + "Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.", splice.sourcePos) 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` + + /** Add cast to force boundaries where T and ~t (an alias of T) are used to ensure PCP. + * '{ ~(...: T) } --> '{ ~(...: T).asInstanceOf[T] } --> '{ ~(...: T).asInstanceOf[~t] } + */ + protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = { + val tp = checkType(tree.sourcePos).apply(tree.tpe.widenTermRefExpr) + tree.asInstance(tp).withSpan(tree.span) + } + + /** If `tree` refers to a locally defined symbol (either directly, or in a pickled type), + * check that its staging level matches the current level. References to types + * that are phase-incorrect can still be healed as follows: * - * 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) - * } + * If `T` is a reference to a type at the wrong level, try to heal it by replacing it with + * `~implicitly[quoted.Type[T]]`. */ - 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 - } - val refSym = captured.getOrElseUpdate(tree.symbol, newCapture).symbol - ref(refSym).withSpan(tree.span) - } + protected def checkLevel(tree: Tree)(implicit ctx: Context): Tree = { + def checkTp(tp: Type): Type = checkType(tree.sourcePos).apply(tp) + tree match { + case Quoted(_) | Spliced(_) => + tree + case tree: RefTree if tree.symbol.is(InlineParam) => + tree + case _: This => + assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty) + tree + case _: Ident => + checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos) match { + case Some(tpRef) => tpRef + case _ => tree } - ) + case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply => + tree.withType(checkTp(tree.tpe)) + case Select(_, OuterSelectName(_, _)) => + tree.withType(checkTp(tree.tpe.widen)) + case _: ValOrDefDef | _: Bind => + tree.symbol.info = checkTp(tree.symbol.info) + tree + case _: Template => + checkTp(tree.symbol.owner.asClass.givenSelfType) + tree + case _ => + tree } - /* 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) + /** Check and heal all named types and this-types in a given type for phase consistency. */ + private def checkType(pos: SourcePosition)(implicit ctx: Context): TypeMap = new TypeMap { + def apply(tp: Type): Type = reporting.trace(i"check type level $tp at $level") { + tp match { + case tp: TypeRef if tp.symbol.isSplice => + if (tp.isTerm) + ctx.error(i"splice outside quotes", pos) + tp + case tp: NamedType => + checkSymLevel(tp.symbol, tp, pos) match { + case Some(tpRef) => tpRef.tpe + case _ => + if (tp.symbol.is(Param)) tp + else mapOver(tp) + } + case tp: ThisType => + assert(checkSymLevel(tp.cls, tp, pos).isEmpty) + mapOver(tp) + case _ => + mapOver(tp) + } + } } - /** 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. + /** Check reference to `sym` for phase consistency, where `tp` is the underlying type + * by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it. + * + * @return `None` if the phase is correct or cannot be healed + * `Some(tree)` with the `tree` of the healed type tree for `~implicitly[quoted.Type[T]]` */ - private def split(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = { - val tree1 = if (inQuote) addTags(transform(tree)) else makeLambda(tree) - (tree1, embedded.getTrees) + private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { + val isThis = tp.isInstanceOf[ThisType] + if (!isThis && sym.maybeOwner.isType && !sym.is(Param)) + checkSymLevel(sym.owner, sym.owner.thisType, pos) + else if (sym.exists && !sym.isStaticOwner && !levelOK(sym)) + tryHeal(sym, tp, pos) + else + None } - /** Register `body` as an `embedded` quote or splice - * and return a hole with `splices` as arguments and the given type `tpe`. + /** Does the level of `sym` match the current level? + * An exception is made for inline vals in macros. These are also OK if their level + * is one higher than the current level, because on execution such values + * are constant expression trees and we can pull out the constant from the tree. */ - 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] + private def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf(sym) match { + case Some(l) => + l == level || + level == -1 && ( + sym == defn.TastyReflection_macroContext || + // here we assume that Splicer.canBeSpliced was true before going to level -1, + // this implies that all non-inline arguments are quoted and that the following two cases are checked + // on inline parameters or type parameters. + sym.is(Param) || + sym.isClass // reference to this in inline methods + ) + case None => + !sym.is(Param) || levelOK(sym.owner) } - 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_~).withSpan(tree.span)) - 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 => - if (enclosingInlineds.nonEmpty) - return EmptyTree // Already checked at definition site and already inlined - markDef(tree) - tree.rhs match { - case InlineSplice(_) => - mapOverTree(enteredSyms) // Ignore output, only check PCP - cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + /** Try to heal phase-inconsistent reference to type `T` using a local type definition. + * @return None if successful + * @return Some(msg) if unsuccessful where `msg` is a potentially empty error message + * to be added to the "inconsistent phase" message. + */ + protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = { + def levelError(errMsg: String) = { + def symStr = + if (!tp.isInstanceOf[ThisType]) sym.show + else if (sym.is(ModuleClass)) sym.sourceModule.show + else i"${sym.name}.this" + ctx.error( + em"""access to $symStr from wrong staging level: + | - the definition is at level ${levelOf(sym).getOrElse(0)}, + | - but the access is at level $level.$errMsg""", pos) + None + } + tp match { + case tp: TypeRef => + if (level == -1) { + assert(ctx.inInlineMethod) + None + } else { + val reqType = defn.QuotedTypeType.appliedTo(tp) + val tag = ctx.typer.inferImplicitArg(reqType, pos.span) + tag.tpe match { + case fail: SearchFailureType => + levelError(i""" + | + | The access would be accepted with the right type tag, but + | ${ctx.typer.missingArgMsg(tag, reqType, "")}""") case _ => - ctx.error( - """Malformed macro. - | - |Expected the ~ to be at the top of the RHS: - | inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y)) - | - | * The contents of the splice must call a static method - | * All arguments must be quoted or inline - """.stripMargin, tree.rhs.sourcePos) - EmptyTree + Some(tag.select(tpnme.UNARY_~)) } - 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) + } + case _ => + levelError("") } } /** InlineSplice is used to detect cases where the expansion * consists of a (possibly multiple & nested) block or a sole expression. */ - object InlineSplice { + private object InlineSplice { def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match { - case Select(qual, _) if tree.symbol.isSplice && Splicer.canBeSpliced(qual) => Some(qual) + case Spliced(code) if Splicer.canBeSpliced(code) => Some(code) case Block(List(stat), Literal(Constant(()))) => unapply(stat) case Block(Nil, expr) => unapply(expr) case Typed(expr, _) => unapply(expr) case _ => None } } + } + } object Staging { - import tpd._ - 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)" - - } } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala new file mode 100644 index 000000000000..c98ee9f33949 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -0,0 +1,145 @@ +package dotty.tools.dotc +package transform + +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{TreeMapWithImplicits, TreeTypeMap, tpd, untpd} +import dotty.tools.dotc.core.Constants._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.quoted._ +import dotty.tools.dotc.core.NameKinds._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.tasty.TreePickler.Hole +import dotty.tools.dotc.util.Spans._ +import dotty.tools.dotc.util.{Property, SourcePosition} +import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.typer.Implicits.SearchFailureType +import dotty.tools.dotc.typer.Inliner + +import scala.collection.mutable +import scala.annotation.constructorOnly + +/** The main transformer class + * @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 + */ +abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMapWithImplicits { + import tpd._ + import TreeMapWithStages._ + + /** A map from locally defined symbols to their definition quotation level */ + private[this] val levelOfMap: mutable.HashMap[Symbol, Int] = ictx.property(LevelOfKey).get + + /** A stack of entered symbols, to be unwound after scope exit */ + private[this] var enteredSyms: List[Symbol] = Nil + + /** The quotation level of the definition of the locally defined symbol */ + protected def levelOf(sym: Symbol): Option[Int] = levelOfMap.get(sym) + + /** Localy defined symbols seen so far by `StagingTransformer.transform` */ + protected def localSymbols: List[Symbol] = enteredSyms + + /** Enter staging level of symbol defined by `tree`, if applicable. */ + private def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match { + case tree: DefTree => + val sym = tree.symbol + if ((sym.isClass || !sym.maybeOwner.isType) && !levelOfMap.contains(sym)) { + levelOfMap(sym) = level + enteredSyms = sym :: enteredSyms + } + case _ => + } + + /** Transform the quote `quote` which contains the quoted `body`. */ + protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + quote match { + case quote: Apply => cpy.Apply(quote)(quote.fun, body :: Nil) + case quote: TypeApply => cpy.TypeApply(quote)(quote.fun, body :: Nil) + } + } + + /** Transform the splice `splice`. */ + protected def splice(splice: Select)(implicit ctx: Context): Tree + + override def transform(tree: Tree)(implicit ctx: Context): Tree = { + reporting.trace(i"StagingTransformer.transform $tree at $level", show = true) { + def mapOverTree(lastEntered: List[Symbol]) = + try super.transform(tree) + finally + while (enteredSyms ne lastEntered) { + levelOfMap -= enteredSyms.head + enteredSyms = enteredSyms.tail + } + + tree match { + case Quoted(Spliced(t)) => + transform(t) // '(~x) --> x + + case Quoted(quotedTree) => + quotation(quotedTree, tree) + + case Spliced(Quoted(quotedTree)) => + transform(quotedTree) // ~('x) --> x + + case tree @ Spliced(_) => + splice(tree) + + 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 _ => + markDef(tree) + mapOverTree(enteredSyms) + } + } + } +} + + +object TreeMapWithStages { + import tpd._ + + /** A key to be used in a context property that caches the `levelOf` mapping */ + private val LevelOfKey = new Property.Key[mutable.HashMap[Symbol, Int]] + + /** A key to be used in a context property that tracks the quoteation level */ + private val QuotationLevel = new Property.Key[Int] + + /** All enclosing calls that are currently inlined, from innermost to outermost. */ + def level(implicit ctx: Context): Int = + ctx.property(QuotationLevel).getOrElse(0) + + /** Context with an incremented quotation level. */ + def quoteContext(implicit ctx: Context): Context = + ctx.fresh.setProperty(QuotationLevel, level + 1) + + /** Context with a decremented quotation level. */ + def spliceContext(implicit ctx: Context): Context = + ctx.fresh.setProperty(QuotationLevel, level - 1) + + /** Initial context for a StagingTransformer transformation. */ + def freshStagingContext(implicit ctx: Context): Context = + ctx.fresh.setProperty(LevelOfKey, new mutable.HashMap[Symbol, Int]) + +} \ No newline at end of file diff --git a/library/src-bootstrapped/scala/StagedTuple.scala b/library/src-bootstrapped/scala/StagedTuple.scala index 96ef7ab31e79..b64d39794b0a 100644 --- a/library/src-bootstrapped/scala/StagedTuple.scala +++ b/library/src-bootstrapped/scala/StagedTuple.scala @@ -251,7 +251,7 @@ object StagedTuple { def as[T: Type]: Expr[T] = '{ (~expr).asInstanceOf[T] } - def bind[T](in: Expr[U] => Expr[T]): Expr[T] = '{ + def bind[T: Type](in: Expr[U] => Expr[T]): Expr[T] = '{ val t: U = (~expr) ~(in('(t))) } diff --git a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala index 4144a767977c..a6a2a31f0d6c 100644 --- a/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala +++ b/library/src-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala @@ -8,15 +8,21 @@ trait TreeUtils { val reflect: Reflection import reflect._ - def let(rhs: Term)(in: Term.Ident => Term): Term = { + /** Bind the `rhs` to a `val` and use it in `body` */ + def let(rhs: Term)(bodyType: Type)(body: Term.Ident => Term): Term = { + // Recover all lost type information type T // TODO probably it is better to use the Sealed contruct rather than let the user create their own existential type + type U // TODO probably it is better to use the Sealed contruct rather than let the user create their own existential type + implicit val bodyTpe: quoted.Type[U] = bodyType.seal.asInstanceOf[quoted.Type[U]] implicit val rhsTpe: quoted.Type[T] = rhs.tpe.seal.asInstanceOf[quoted.Type[T]] val rhsExpr = rhs.seal[T] - val expr = '{ - val x = ~rhsExpr - ~in(('(x)).unseal.asInstanceOf[Term.Ident]).seal[Any] - } - expr.unseal + let[T, U](rhsExpr)(x => body(x.unseal.asInstanceOf[Term.Ident]).seal[U]).unseal + } + + /** */ + private def let[T: quoted.Type, U: quoted.Type](rhs: Expr[T])(in: Expr[T] => Expr[U]): Expr[U] = '{ + val x = ~rhs + ~in('(x)) } } diff --git a/library/src-non-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala b/library/src-non-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala index d532aa2883f6..8fb39d2b2d02 100644 --- a/library/src-non-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala +++ b/library/src-non-bootstrapped/scala/tasty/reflect/utils/TreeUtils.scala @@ -6,6 +6,8 @@ trait TreeUtils { val reflect: Reflection import reflect._ - def let(rhs: Term)(in: Term.Ident => Term): Term = throw new Exception("non bootstrpped lib") + /** Bind the `rhs` to a `val` and use it in `body` */ + def let(rhs: Term)(bodyType: Type)(body: Term.Ident => Term): Term = + throw new Exception("non bootstrpped lib") } diff --git a/tests/neg/quote-0.scala b/tests/neg/quote-0.scala index 3e4231281300..e6eae3dfe802 100644 --- a/tests/neg/quote-0.scala +++ b/tests/neg/quote-0.scala @@ -14,8 +14,8 @@ class Test { '((y: Expr[Int]) => ~y ) // error: wrong staging level - def f[T](t: Type[T], x: Expr[T]) = '{ // error: wrong staging level - val z2 = ~x + def f[T](t: Type[T], x: Expr[T]) = '{ + val z2 = ~x // error // error: wrong staging level } def g[T](implicit t: Type[T], x: Expr[T]) = '{ diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index fb27821f59e7..4401c849bcf7 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 diff --git a/tests/pos/i4023c/Macro_1.scala b/tests/pos/i4023c/Macro_1.scala index 7e5714514b8d..5888856ff83e 100644 --- a/tests/pos/i4023c/Macro_1.scala +++ b/tests/pos/i4023c/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T](x: T): T = ~impl('(x), '[T]) - def impl[T](x: Expr[T], t: Type[T]): Expr[T] = '{ (~x): ~t } + inline def ff[T](x: T): T = ~impl('(x))('[T]) + def impl[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = '{ (~x): ~t } } diff --git a/tests/pos/i4774e.scala b/tests/pos/i4774e.scala new file mode 100644 index 000000000000..d836c86093d8 --- /dev/null +++ b/tests/pos/i4774e.scala @@ -0,0 +1,10 @@ + +import scala.quoted._ + +object Test { + def loop[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = + '{ def y = ~x; ~loop('(y)) } + + def loop2[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = + '{ def y() = ~x; ~loop('(y())) } +} diff --git a/tests/pos/i4774f.scala b/tests/pos/i4774f.scala new file mode 100644 index 000000000000..37b6031e8fb6 --- /dev/null +++ b/tests/pos/i4774f.scala @@ -0,0 +1,10 @@ + +import scala.quoted._ + +object Test { + def loop[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = + '{ def y: T = ~x; ~loop('(y)) } + + def loop2[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = + '{ def y(): T = ~x; ~loop('(y())) } +} diff --git a/tests/pos/quote-bind-T.scala b/tests/pos/quote-bind-T.scala new file mode 100644 index 000000000000..482eafc0a49e --- /dev/null +++ b/tests/pos/quote-bind-T.scala @@ -0,0 +1,10 @@ + +import scala.quoted._ + +object Test { + def matchX[T](x: Expr[T])(implicit t: Type[T]): Expr[T] = '{ + (~x) match { + case y: T => y + } + } +} diff --git a/tests/pos/quote-liftable-list-2.scala b/tests/pos/quote-liftable-list-2.scala new file mode 100644 index 000000000000..e249c388d475 --- /dev/null +++ b/tests/pos/quote-liftable-list-2.scala @@ -0,0 +1,13 @@ +import scala.quoted._ + +object Test { + + implicit def ListIsLiftableOr[T: Type, U: Type]: Liftable[List[T | U]] = new { + def toExpr(xs: List[T | U]): Expr[List[T | U]] = '(Nil: List[T | U]) + } + + implicit def ListIsLiftableAnd[T: Type, U: Type]: Liftable[List[T & U]] = new { + def toExpr(xs: List[T & U]): Expr[List[T & U]] = '(Nil: List[T & U]) + } + +} diff --git a/tests/pos/quote-liftable-list.scala b/tests/pos/quote-liftable-list.scala new file mode 100644 index 000000000000..fdefe80801a4 --- /dev/null +++ b/tests/pos/quote-liftable-list.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object Test { + + implicit def ListIsLiftable[T: Liftable: Type]: Liftable[List[T]] = new { + def toExpr(xs: List[T]): Expr[List[T]] = '(Nil: List[T]) + } + +} diff --git a/tests/run-with-compiler-custom-args/staged-streams_1.scala b/tests/run-with-compiler-custom-args/staged-streams_1.scala index eac8d5f3c003..101433ddf317 100644 --- a/tests/run-with-compiler-custom-args/staged-streams_1.scala +++ b/tests/run-with-compiler-custom-args/staged-streams_1.scala @@ -13,7 +13,7 @@ object Test { } object Var { - def apply[T: Type, U](init: Expr[T])(body: Var[T] => Expr[U]): Expr[U] = '{ + def apply[T: Type, U: Type](init: Expr[T])(body: Var[T] => Expr[U]): Expr[U] = '{ var x = ~init ~body( new Var[T] { diff --git a/tests/run-with-compiler/i3823-b.scala b/tests/run-with-compiler/i3823-b.scala index f12d6dbd9141..cdb5c4eb3bd9 100644 --- a/tests/run-with-compiler/i3823-b.scala +++ b/tests/run-with-compiler/i3823-b.scala @@ -2,7 +2,7 @@ import scala.quoted.Toolbox.Default._ import scala.quoted._ object Test { def main(args: Array[String]): Unit = { - def f[T](x: Expr[T])(t: Type[T]) = '{ + def f[T](x: Expr[T])(implicit t: Type[T]) = '{ val z: t.unary_~ = ~x } println(f('(2))(Type.IntTag).show) diff --git a/tests/run-with-compiler/i3823.scala b/tests/run-with-compiler/i3823.scala index 55a4f88256c3..dc593a1e1715 100644 --- a/tests/run-with-compiler/i3823.scala +++ b/tests/run-with-compiler/i3823.scala @@ -2,7 +2,7 @@ import scala.quoted.Toolbox.Default._ import scala.quoted._ object Test { def main(args: Array[String]): Unit = { - def f[T](x: Expr[T])(t: Type[T]) = '{ + def f[T: Type](x: Expr[T])(t: Type[T]) = '{ val z: t.unary_~ = ~x } println(f('(2))('[Int]).show) diff --git a/tests/run-with-compiler/i3947.scala b/tests/run-with-compiler/i3947.scala index bd7f035fc2ae..e498cfe9f618 100644 --- a/tests/run-with-compiler/i3947.scala +++ b/tests/run-with-compiler/i3947.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947b.scala b/tests/run-with-compiler/i3947b.scala index 9c05028d2a51..ab30d1fd9847 100644 --- a/tests/run-with-compiler/i3947b.scala +++ b/tests/run-with-compiler/i3947b.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947b2.scala b/tests/run-with-compiler/i3947b2.scala index bfdc28ebb762..aad8ab6d7dc2 100644 --- a/tests/run-with-compiler/i3947b2.scala +++ b/tests/run-with-compiler/i3947b2.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947b3.scala b/tests/run-with-compiler/i3947b3.scala index 4ad8cde804d1..0d34734a7aa0 100644 --- a/tests/run-with-compiler/i3947b3.scala +++ b/tests/run-with-compiler/i3947b3.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947c.scala b/tests/run-with-compiler/i3947c.scala index 0c10f5aec330..4bb6a829605f 100644 --- a/tests/run-with-compiler/i3947c.scala +++ b/tests/run-with-compiler/i3947c.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947d.scala b/tests/run-with-compiler/i3947d.scala index c5acce46f697..33466ceccede 100644 --- a/tests/run-with-compiler/i3947d.scala +++ b/tests/run-with-compiler/i3947d.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947d2.scala b/tests/run-with-compiler/i3947d2.scala index 9306704dbcaa..cac86cfa42f9 100644 --- a/tests/run-with-compiler/i3947d2.scala +++ b/tests/run-with-compiler/i3947d2.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947e.scala b/tests/run-with-compiler/i3947e.scala index c8e9c24d651a..1a84025cbb32 100644 --- a/tests/run-with-compiler/i3947e.scala +++ b/tests/run-with-compiler/i3947e.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947f.scala b/tests/run-with-compiler/i3947f.scala index 61063dfece13..67b83d7edcfa 100644 --- a/tests/run-with-compiler/i3947f.scala +++ b/tests/run-with-compiler/i3947f.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947g.scala b/tests/run-with-compiler/i3947g.scala index 81a04f2e15e3..9132a459d1c3 100644 --- a/tests/run-with-compiler/i3947g.scala +++ b/tests/run-with-compiler/i3947g.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947i.scala b/tests/run-with-compiler/i3947i.scala index 6b314669009f..ae07aaebfed9 100644 --- a/tests/run-with-compiler/i3947i.scala +++ b/tests/run-with-compiler/i3947i.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i3947j.scala b/tests/run-with-compiler/i3947j.scala index 1adaeabbb7dd..2e4045ac5a6f 100644 --- a/tests/run-with-compiler/i3947j.scala +++ b/tests/run-with-compiler/i3947j.scala @@ -7,7 +7,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def test[T](clazz: java.lang.Class[T]): Unit = { + def test[T: Type](clazz: java.lang.Class[T]): Unit = { val lclazz = clazz.toExpr val name = '{ (~lclazz).getCanonicalName } println() diff --git a/tests/run-with-compiler/i4044b.scala b/tests/run-with-compiler/i4044b.scala index 31facbff19ae..66a57073e9ff 100644 --- a/tests/run-with-compiler/i4044b.scala +++ b/tests/run-with-compiler/i4044b.scala @@ -8,7 +8,7 @@ sealed abstract class VarRef[T] { } object VarRef { - def apply[T: Type, U](init: Expr[T])(body: VarRef[T] => Expr[U]): Expr[U] = '{ + def apply[T: Type, U: Type](init: Expr[T])(body: VarRef[T] => Expr[U]): Expr[U] = '{ var x = ~init ~body( new VarRef { diff --git a/tests/run-with-compiler/i4350.check b/tests/run-with-compiler/i4350.check index 45b028ad64cb..e670af3da233 100644 --- a/tests/run-with-compiler/i4350.check +++ b/tests/run-with-compiler/i4350.check @@ -1,2 +1,2 @@ -null.asInstanceOf[lang.Object] -null.asInstanceOf[scala.Predef.String] +(null: scala.Any).asInstanceOf[java.lang.Object] +(null: scala.Any).asInstanceOf[scala.Predef.String] diff --git a/tests/run-with-compiler/i4350.scala b/tests/run-with-compiler/i4350.scala index 126f89df2d6a..6edf550a44bf 100644 --- a/tests/run-with-compiler/i4350.scala +++ b/tests/run-with-compiler/i4350.scala @@ -3,7 +3,7 @@ import scala.quoted.Toolbox.Default._ import scala.quoted.Type class Foo[T: Type] { - def q = '(null.asInstanceOf[T]) + def q = '((null: Any).asInstanceOf[T]) } object Test { diff --git a/tests/run-with-compiler/i5247.check b/tests/run-with-compiler/i5247.check index 16060b4698d5..63524be90ce2 100644 --- a/tests/run-with-compiler/i5247.check +++ b/tests/run-with-compiler/i5247.check @@ -1,2 +1,2 @@ -null.asInstanceOf[lang.Object] -null.asInstanceOf[scala.List[lang.Object]] +null.asInstanceOf[java.lang.Object] +null.asInstanceOf[scala.List[java.lang.Object]] diff --git a/tests/run-with-compiler/quote-lib.scala b/tests/run-with-compiler/quote-lib.scala index 599504c2bb9a..08178e3d58f8 100644 --- a/tests/run-with-compiler/quote-lib.scala +++ b/tests/run-with-compiler/quote-lib.scala @@ -59,11 +59,11 @@ package liftable { } object Lets { - def letVal[T, U](expr: Expr[T])(body: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[U] = + def letVal[T, U: Type](expr: Expr[T])(body: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[U] = '{ val letVal: ~t = ~expr; ~body('(letVal)) } - def letLazyVal[T, U](expr: Expr[T])(body: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[U] = + def letLazyVal[T, U: Type](expr: Expr[T])(body: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[U] = '{ lazy val letLazyVal: ~t = ~expr; ~body('(letLazyVal)) } - def letDef[T, U](expr: Expr[T])(body: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[U] = + def letDef[T, U: Type](expr: Expr[T])(body: Expr[T] => Expr[U])(implicit t: Type[T]): Expr[U] = '{ def letDef: ~t = ~expr; ~body('(letDef)) } } diff --git a/tests/run-with-compiler/quote-type-tags.scala b/tests/run-with-compiler/quote-type-tags.scala index 27e4a86f062c..aeab7a9938fd 100644 --- a/tests/run-with-compiler/quote-type-tags.scala +++ b/tests/run-with-compiler/quote-type-tags.scala @@ -4,7 +4,7 @@ object Test { def main(args: Array[String]): Unit = { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make - def asof[T, U](x: Expr[T], t: Type[U]): Expr[U] = + def asof[T: Type, U](x: Expr[T], t: Type[U]): Expr[U] = '((~x).asInstanceOf[~t]) println(asof('(), '[Unit]).show) diff --git a/tests/run-with-compiler/shonan-hmm/Lifters.scala b/tests/run-with-compiler/shonan-hmm/Lifters.scala index 25b79d2428a4..e852085b2d0c 100644 --- a/tests/run-with-compiler/shonan-hmm/Lifters.scala +++ b/tests/run-with-compiler/shonan-hmm/Lifters.scala @@ -19,7 +19,7 @@ object Lifters { ~initArray(arr, '(array)) } - private def initArray[T : Liftable](arr: Array[T], array: Expr[Array[T]]): Expr[Array[T]] = { + private def initArray[T : Liftable : Type](arr: Array[T], array: Expr[Array[T]]): Expr[Array[T]] = { UnrolledExpr.block( arr.zipWithIndex.map { case (x, i) => '{ (~array)(~i.toExpr) = ~x.toExpr } diff --git a/tests/run-with-compiler/shonan-hmm/MVmult.scala b/tests/run-with-compiler/shonan-hmm/MVmult.scala index f483b856e3c5..a476c1919831 100644 --- a/tests/run-with-compiler/shonan-hmm/MVmult.scala +++ b/tests/run-with-compiler/shonan-hmm/MVmult.scala @@ -99,7 +99,7 @@ object MVmult { } } - def initRows[T](a: Array[Array[Int]])(cont: Array[Expr[Array[Int]]] => Expr[T]): Expr[T] = { + def initRows[T: Type](a: Array[Array[Int]])(cont: Array[Expr[Array[Int]]] => Expr[T]): Expr[T] = { import Lifters._ def loop(i: Int, acc: List[Expr[Array[Int]]]): Expr[T] = { if (i >= a.length) cont(acc.toArray.reverse) diff --git a/tests/run-with-compiler/shonan-hmm/UnrolledExpr.scala b/tests/run-with-compiler/shonan-hmm/UnrolledExpr.scala index 72877d2ad483..b76edae7fd92 100644 --- a/tests/run-with-compiler/shonan-hmm/UnrolledExpr.scala +++ b/tests/run-with-compiler/shonan-hmm/UnrolledExpr.scala @@ -8,7 +8,7 @@ object UnrolledExpr { } // TODO support blocks in the compiler to avoid creating trees of blocks? - def block[T](stats: Iterable[Expr[_]], expr: Expr[T]): Expr[T] = { + def block[T: Type](stats: Iterable[Expr[_]], expr: Expr[T]): Expr[T] = { def rec(stats: List[Expr[_]]): Expr[T] = stats match { case x :: xs => '{ ~x; ~rec(xs) } case Nil => expr diff --git a/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala b/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala index 793cb85c9e47..37806ce1851a 100644 --- a/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala +++ b/tests/run-with-compiler/tasty-unsafe-let/quoted_1.scala @@ -13,7 +13,7 @@ object Macros { val rhsTerm = rhs.unseal import reflect.util.{let => letTerm} - letTerm(rhsTerm) { rhsId => + letTerm(rhsTerm)(('[Unit]).unseal.tpe) { rhsId => body(rhsId.seal[Any].asInstanceOf[Expr[T]]).unseal // Dangerous uncheked cast! }.seal[Unit] } diff --git a/tests/run/gestalt-optional-staging/Macro_1.scala b/tests/run/gestalt-optional-staging/Macro_1.scala index 19aff30b7315..74b2538782bc 100644 --- a/tests/run/gestalt-optional-staging/Macro_1.scala +++ b/tests/run/gestalt-optional-staging/Macro_1.scala @@ -17,12 +17,12 @@ final class Optional[+A >: Null](val value: A) extends AnyVal { object Optional { // FIXME fix issue #5097 and enable private - /*private*/ def getOrElseImpl[T >: Null](opt: Expr[Optional[T]], alt: Expr[T]): Expr[T] = '{ + /*private*/ def getOrElseImpl[T >: Null : Type](opt: Expr[Optional[T]], alt: Expr[T]): Expr[T] = '{ if ((~opt).isEmpty) ~alt else (~opt).value } // FIXME fix issue #5097 and enable private - /*private*/ def mapImpl[A >: Null, B >: Null : Type](opt: Expr[Optional[A]], f: Expr[A => B]): Expr[Optional[B]] = '{ + /*private*/ def mapImpl[A >: Null : Type, B >: Null : Type](opt: Expr[Optional[A]], f: Expr[A => B]): Expr[Optional[B]] = '{ if ((~opt).isEmpty) new Optional(null) else new Optional(~f('((~opt).value))) } From 76f4c2c39fa35932c46adbe0bfcdbb60936efbd0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Feb 2019 09:58:51 +0100 Subject: [PATCH 02/12] Cleanup imports and fix documentation --- .../tools/dotc/ast/TreeMapWithImplicits.scala | 25 +++++++++++-------- .../tools/dotc/transform/ReifyQuotes.scala | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala b/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala index 4d69a1538f1c..a37173e3d5f9 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala @@ -1,21 +1,24 @@ -package dotty.tools.dotc -package ast +package dotty.tools.dotc.ast -import core._ -import ast.Trees._ -import Contexts._ -import Symbols._ -import annotation.tailrec +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.TypeError -/** A TreeMap that maintains the necessary infrastructore to support +import scala.annotation.tailrec + +/** A TreeMap that maintains the necessary infrastructure to support * contxtual implicit searches (type-scope implicits are supported anyway). + * + * This incudes impicits defined in scope as well as imported implicits. */ -class TreeMapWithImplicits extends ast.tpd.TreeMap { - import ast.tpd._ +class TreeMapWithImplicits extends tpd.TreeMap { + import tpd._ protected def localCtx(tree: Tree)(implicit ctx: Context): FreshContext = { val sym = tree.symbol - val owner = if (sym is Flags.PackageVal) sym.moduleClass else sym + val owner = if (sym is PackageVal) sym.moduleClass else sym ctx.fresh.setTree(tree).setOwner(owner) } diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 0d274ffe7e01..787268de18ca 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -303,7 +303,7 @@ class ReifyQuotes extends MacroTransform { } ) } - /* Lambdas are generated outside the quote that is beeing reified (i.e. in outer.rctx.owner). + /* Lambdas are generated outside the quote that is beeing reified (i.e. in outer.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. */ From 7525d59133d7dbdadc2112f59254b578f427a0a2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Feb 2019 10:38:06 +0100 Subject: [PATCH 03/12] Use same local contexts as in TreeMap --- .../dotty/tools/dotc/ast/TreeMapWithImplicits.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala b/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala index a37173e3d5f9..e98e72978281 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala @@ -16,12 +16,6 @@ import scala.annotation.tailrec class TreeMapWithImplicits extends tpd.TreeMap { import tpd._ - protected def localCtx(tree: Tree)(implicit ctx: Context): FreshContext = { - val sym = tree.symbol - val owner = if (sym is PackageVal) sym.moduleClass else sym - ctx.fresh.setTree(tree).setOwner(owner) - } - def transformSelf(vd: ValDef)(implicit ctx: Context): ValDef = cpy.ValDef(vd)(tpt = transform(vd.tpt)) @@ -75,11 +69,13 @@ class TreeMapWithImplicits extends tpd.TreeMap { } override def transform(tree: Tree)(implicit ctx: Context): Tree = { + def localCtx = + if (tree.hasType && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx try tree match { case tree: Block => super.transform(tree)(nestedScopeCtx(tree.stats)) case tree: DefDef => - implicit val ctx1 = localCtx(tree)(ctx) + implicit val ctx = localCtx cpy.DefDef(tree)( tree.name, transformSub(tree.tparams), @@ -89,7 +85,7 @@ class TreeMapWithImplicits extends tpd.TreeMap { case EmptyValDef => tree case _: PackageDef | _: MemberDef => - super.transform(tree)(localCtx(tree)) + super.transform(tree)(localCtx) case impl @ Template(constr, parents, self, _) => cpy.Template(tree)( transformSub(constr), From 02ff72f529f14089dbd6f7bd9f1e1e61b9e42ab9 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 6 Feb 2019 14:45:38 +0100 Subject: [PATCH 04/12] Fix typo --- compiler/src/dotty/tools/dotc/transform/Staging.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index b3b0d3dc5c1c..2af65242f2d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -130,7 +130,7 @@ class Staging extends MacroTransform { } /** Transform splice - * - If inside a quote, transform transform the contents of the splice. + * - If inside a quote, transform the contents of the splice. * - If inside inlined code, expand the macro code. * - If inside of a macro definition, check the validity of the macro. */ From b71326c210a34d5ae474b7cf24bfc069c7022706 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:21:38 +0100 Subject: [PATCH 05/12] Move Quoted and Spliced extractors to TreeInfo --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 23 +++++++++++++++++++ .../dotty/tools/dotc/core/quoted/Quoted.scala | 20 ---------------- .../tools/dotc/core/quoted/Spliced.scala | 19 --------------- 3 files changed, 23 insertions(+), 39 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala delete mode 100644 compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index eadd5b92206f..b4637ff1516d 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -7,6 +7,7 @@ import Flags._, Trees._, Types._, Contexts._ import Names._, StdNames._, NameOps._, Symbols._ import typer.ConstFold import reporting.trace +import dotty.tools.dotc.transform.SymUtils._ import scala.annotation.tailrec @@ -815,6 +816,28 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } } } + + /** Extractors for quotes */ + object Quoted { + /** Extracts the content of a quoted tree. + * The result can be the contents of a term or type quote, which + * will return a term or type tree respectively. + */ + def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[tpd.Tree] = tree match { + case tree: GenericApply[Type] if tree.symbol.isQuote => Some(tree.args.head) + case _ => None + } + } + + /** Extractors for splices */ + object Spliced { + /** Extracts the content of a spliced tree. + * The result can be the contents of a term or type splice, which + * will return a term or type tree respectively. + */ + def unapply(tree: tpd.Select)(implicit ctx: Context): Option[tpd.Tree] = + if (tree.symbol.isSplice) Some(tree.qualifier) else None + } } object TreeInfo { diff --git a/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala b/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala deleted file mode 100644 index e2aa1b07f604..000000000000 --- a/compiler/src/dotty/tools/dotc/core/quoted/Quoted.scala +++ /dev/null @@ -1,20 +0,0 @@ -package dotty.tools.dotc.core.quoted - -import dotty.tools.dotc.ast.Trees.GenericApply -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Types.Type -import dotty.tools.dotc.transform.SymUtils._ - -/** Extractors for quotes */ -object Quoted { - - /** Extracts the content of a quoted tree. - * The result can be the contents of a term or type quote, which - * will return a term or type tree respectively. - */ - def unapply(tree: tpd.Tree)(implicit ctx: Context): Option[tpd.Tree] = tree match { - case tree: GenericApply[Type] if tree.symbol.isQuote => Some(tree.args.head) - case _ => None - } -} diff --git a/compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala b/compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala deleted file mode 100644 index 95bbfd07bae7..000000000000 --- a/compiler/src/dotty/tools/dotc/core/quoted/Spliced.scala +++ /dev/null @@ -1,19 +0,0 @@ -package dotty.tools.dotc.core.quoted - -import dotty.tools.dotc.ast.Trees._ -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.Types.Type -import dotty.tools.dotc.transform.SymUtils._ - -/** Extractors for splices */ -object Spliced { - - /** Extracts the content of a spliced tree. - * The result can be the contents of a term or type splice, which - * will return a term or type tree respectively. - */ - def unapply(tree: tpd.Select)(implicit ctx: Context): Option[tpd.Tree] = - if (tree.symbol.isSplice) Some(tree.qualifier) else None - -} From f9705a64adb14f8427b1b715dfce6dbab07a4aa1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:36:55 +0100 Subject: [PATCH 06/12] Refactor printer code --- .../src/dotty/tools/dotc/printing/RefinedPrinter.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 23b5cb9a8041..ca97ba0f0eef 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -252,10 +252,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ("{" ~ toText(trees, "\n") ~ "}").close protected def typeApplyText[T >: Untyped](tree: TypeApply[T]): Text = { - if (tree.fun.hasType && tree.fun.symbol == defn.QuotedType_apply) - keywordStr("'[") ~ toTextGlobal(tree.args, ", ") ~ keywordStr("]") - else - toTextLocal(tree.fun) ~ "[" ~ toTextGlobal(tree.args, ", ") ~ "]" + val isQuote = tree.fun.hasType && tree.fun.symbol == defn.QuotedType_apply + val (open, close) = if (isQuote) (keywordStr("'["), keywordStr("]")) else ("[", "]") + toTextLocal(tree.fun).provided(!isQuote) ~ open ~ toTextGlobal(tree.args, ", ") ~ close } protected def toTextCore[T >: Untyped](tree: Tree[T]): Text = { From c115a7b9ee3d1058151cab1533ab3d552b11a536 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:38:39 +0100 Subject: [PATCH 07/12] `!sym.maybeOwner.isType` -> `sym.maybeOwber.isTerm` --- compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index c98ee9f33949..07583088d239 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -48,7 +48,7 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap private def markDef(tree: Tree)(implicit ctx: Context): Unit = tree match { case tree: DefTree => val sym = tree.symbol - if ((sym.isClass || !sym.maybeOwner.isType) && !levelOfMap.contains(sym)) { + if ((sym.isClass || sym.maybeOwner.isTerm) && !levelOfMap.contains(sym)) { levelOfMap(sym) = level enteredSyms = sym :: enteredSyms } From 9dfb21579980fc3c84a9124537cb475a9d7b8e13 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:41:52 +0100 Subject: [PATCH 08/12] Rename `quotation` -> `transformQuotation` and `splice` -> `transformSplice` --- .../src/dotty/tools/dotc/transform/ReifyQuotes.scala | 12 ++++++------ .../src/dotty/tools/dotc/transform/Staging.scala | 6 +++--- .../tools/dotc/transform/TreeMapWithStages.scala | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 787268de18ca..d61b5f9bf0d5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -168,12 +168,12 @@ class ReifyQuotes extends MacroTransform { * `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with * core and splices as arguments. */ - override protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { val isType = quote.symbol eq defn.QuotedType_apply assert(!body.symbol.isSplice) if (level > 0) { val body1 = nested(isQuote = true).transform(body)(quoteContext) - super.quotation(body1, quote) + super.transformQuotation(body1, quote) } else body match { case body: RefTree if isCaptured(body.symbol, level + 1) => @@ -230,7 +230,7 @@ class ReifyQuotes extends MacroTransform { * and make a hole from these parts. Otherwise issue an error, unless we * are in the body of an inline method. */ - protected def splice(splice: Select)(implicit ctx: Context): Tree = { + protected def transformSplice(splice: Select)(implicit ctx: Context): Tree = { if (level > 1) { val body1 = nested(isQuote = false).transform(splice.qualifier)(spliceContext) body1.select(splice.name) @@ -357,15 +357,15 @@ class ReifyQuotes extends MacroTransform { case TypeApply(Select(spliceTree @ Spliced(_), _), tp) if tree.symbol == defn.Any_asInstanceOf => // Splice term which should be in the form `x.unary_~.asInstanceOf[T]` where T is an artefact of // typer to allow pickling/unpickling phase consistent types - splice(spliceTree) + transformSplice(spliceTree) case tree: TypeTree if tree.tpe.typeSymbol.isSplice => val splicedType = tree.tpe.stripTypeVar.asInstanceOf[TypeRef].prefix.termSymbol - splice(ref(splicedType).select(tpnme.UNARY_~).withSpan(tree.span)) + transformSplice(ref(splicedType).select(tpnme.UNARY_~).withSpan(tree.span)) 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_~)) + transformSplice(t.select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) case tree: DefDef if tree.symbol.is(Macro) && level == 0 => // Shrink size of the tree. The methods have already been inlined. diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 2af65242f2d0..52314b0d89c3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -124,9 +124,9 @@ class Staging extends MacroTransform { } /** Transform quoted trees while maintaining phase correctness */ - override protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { val body1 = transform(body)(quoteContext) - super.quotation(body1, quote) + super.transformQuotation(body1, quote) } /** Transform splice @@ -134,7 +134,7 @@ class Staging extends MacroTransform { * - If inside inlined code, expand the macro code. * - If inside of a macro definition, check the validity of the macro. */ - protected def splice(splice: Select)(implicit ctx: Context): Tree = { + protected def transformSplice(splice: Select)(implicit ctx: Context): Tree = { if (level >= 1) { val body1 = transform(splice.qualifier)(spliceContext) val splice1 = cpy.Select(splice)(body1, splice.name) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala index 07583088d239..a7cba635b7f0 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeMapWithStages.scala @@ -56,7 +56,7 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap } /** Transform the quote `quote` which contains the quoted `body`. */ - protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { + protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = { quote match { case quote: Apply => cpy.Apply(quote)(quote.fun, body :: Nil) case quote: TypeApply => cpy.TypeApply(quote)(quote.fun, body :: Nil) @@ -64,7 +64,7 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap } /** Transform the splice `splice`. */ - protected def splice(splice: Select)(implicit ctx: Context): Tree + protected def transformSplice(splice: Select)(implicit ctx: Context): Tree override def transform(tree: Tree)(implicit ctx: Context): Tree = { reporting.trace(i"StagingTransformer.transform $tree at $level", show = true) { @@ -81,13 +81,13 @@ abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMap transform(t) // '(~x) --> x case Quoted(quotedTree) => - quotation(quotedTree, tree) + transformQuotation(quotedTree, tree) case Spliced(Quoted(quotedTree)) => transform(quotedTree) // ~('x) --> x case tree @ Spliced(_) => - splice(tree) + transformSplice(tree) case Block(stats, _) => val last = enteredSyms From 8374c4d27ad64e34053e833949dee738c0d053ef Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:44:03 +0100 Subject: [PATCH 09/12] Shorter names for quotation tags --- 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 d61b5f9bf0d5..b6b18e692952 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -133,7 +133,7 @@ class ReifyQuotes extends MacroTransform { val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs) val local = ctx.newSymbol( owner = ctx.owner, - name = UniqueName.fresh((splicedTree.symbol.name.toString + "$" + nme.UNARY_~).toTermName).toTypeName, + name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_~").toTermName).toTypeName, flags = Synthetic, info = TypeAlias(splicedTree.tpe.select(tpnme.UNARY_~)), coord = spliced.termSymbol.coord).asType From b523a050f25d72cdfaaf7e91d8039ae916f8e069 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:45:32 +0100 Subject: [PATCH 10/12] Simplify handling of pimitive types --- .../src/dotty/tools/dotc/transform/ReifyQuotes.scala | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index b6b18e692952..e39e1b174d9d 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -209,15 +209,7 @@ class ReifyQuotes extends MacroTransform { 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") + if (body.symbol.isPrimitiveValueClass) tag(s"${body.symbol.name}Tag") else pickleAsTasty() } else toValue(body) match { From bef8cce4bd08935db2a2457d3d4a8edbce16de3d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 12 Feb 2019 10:47:23 +0100 Subject: [PATCH 11/12] Fix typo --- 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 e39e1b174d9d..9ed58eb32c64 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -259,7 +259,7 @@ class ReifyQuotes extends MacroTransform { * * See: `capture` * - * At the same time register `embedded` trees `x` and `y` to place as arguments of the hole + * At the same time register embedded trees `x` and `y` to place as arguments of the hole * placed in the original code. * '{ * val x = ??? From bb9874c4bd40b5e853ae19721898c16d4393e7a1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 14 Feb 2019 07:43:03 +0100 Subject: [PATCH 12/12] Fix after rebase --- .../reflect-select-copy/assert_1.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/run-with-compiler/reflect-select-copy/assert_1.scala b/tests/run-with-compiler/reflect-select-copy/assert_1.scala index 3537676ff1e2..7b0f7ccaa6fd 100644 --- a/tests/run-with-compiler/reflect-select-copy/assert_1.scala +++ b/tests/run-with-compiler/reflect-select-copy/assert_1.scala @@ -15,9 +15,9 @@ object scalatest { cond.unseal.underlyingArgument match { case Term.Apply(sel @ Term.Select(lhs, op), rhs :: Nil) => - let(lhs) { left => - let(rhs) { right => - let(Term.Apply(Term.Select.copy(sel)(left, op), right :: Nil)) { result => + let(lhs)(definitions.UnitType) { left => + let(rhs)(definitions.UnitType) { right => + let(Term.Apply(Term.Select.copy(sel)(left, op), right :: Nil))(definitions.UnitType) { result => val l = left.seal[Any] val r = right.seal[Any] val b = result.seal[Boolean] @@ -28,9 +28,9 @@ object scalatest { }.seal[Unit] case Term.Apply(f @ Term.Apply(Term.IsSelect(sel @ Term.Select(Term.Apply(qual, lhs :: Nil), op)), rhs :: Nil), implicits) if isImplicitMethodType(f.tpe) => - let(lhs) { left => - let(rhs) { right => - let(Term.Apply(Term.Apply(Term.Select.copy(sel)(Term.Apply(qual, left :: Nil), op), right :: Nil), implicits)) { result => + let(lhs)(definitions.UnitType) { left => + let(rhs)(definitions.UnitType) { right => + let(Term.Apply(Term.Apply(Term.Select.copy(sel)(Term.Apply(qual, left :: Nil), op), right :: Nil), implicits))(definitions.UnitType) { result => val l = left.seal[Any] val r = right.seal[Any] val b = result.seal[Boolean]