From fd7ff85c4296c4a72d238a2bad6d187200052db7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 20 Mar 2020 14:25:33 +0100 Subject: [PATCH 1/5] Fix #7110: Pickle types of quote holes To avoid wrong member selection when unpickling we must have qualifiers that have the same type as when it was pickled. This implies that we cannot expand the holes during unpickling as the expansion of a qualifier could have a more precise type. To be able to unpickle a hole without expanding it we must know its type, hence it must be pickled. Once the pickled quote has been unpickled, we first replace all the type splices. After the types are properly set we expand all the term holes. Changes * Types of holes are pickled in TASTy * Pickled quotes are unpickled and then the holes are expanded * Improved debugging info for unpickled quotes --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- .../dotc/core/quoted/PickledQuotes.scala | 129 ++++++++++++++---- .../tools/dotc/core/tasty/TreePickler.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 39 ++---- .../tools/dotc/transform/ReifyQuotes.scala | 32 ++++- .../pos-macros/i7853/JsonEncoder_1.scala | 0 .../i7853/SummonJsonEncoderTest_2.scala | 0 .../pos-macros/i7853/Test_3.scala | 0 tests/pos-macros/i7110a/Macro_1.scala | 15 ++ tests/pos-macros/i7110a/Test_2.scala | 14 ++ tests/pos-macros/i7110b/Macro_1.scala | 16 +++ tests/pos-macros/i7110b/Test_2.scala | 15 ++ tests/pos-macros/i7110c/Macro_1.scala | 14 ++ tests/pos-macros/i7110c/Test_2.scala | 16 +++ tests/pos-macros/i7110d/Macro_1.scala | 14 ++ tests/pos-macros/i7110d/Test_2.scala | 16 +++ tests/pos-macros/i7110e/Macro_1.scala | 15 ++ tests/pos-macros/i7110e/Test_2.scala | 14 ++ tests/pos-macros/i7110f/Macro_1.scala | 15 ++ tests/pos-macros/i7110f/Test_2.scala | 14 ++ tests/pos/quoted-var.scala | 17 +++ tests/run-staging/abstract-int-quote.scala | 16 +++ tests/run-staging/quote-nested-6.check | 7 + tests/run-staging/quote-nested-6.scala | 20 +++ tests/run/i7110.scala | 18 +++ 25 files changed, 403 insertions(+), 59 deletions(-) rename tests/{ => disabled}/pos-macros/i7853/JsonEncoder_1.scala (100%) rename tests/{ => disabled}/pos-macros/i7853/SummonJsonEncoderTest_2.scala (100%) rename tests/{ => disabled}/pos-macros/i7853/Test_3.scala (100%) create mode 100644 tests/pos-macros/i7110a/Macro_1.scala create mode 100644 tests/pos-macros/i7110a/Test_2.scala create mode 100644 tests/pos-macros/i7110b/Macro_1.scala create mode 100644 tests/pos-macros/i7110b/Test_2.scala create mode 100644 tests/pos-macros/i7110c/Macro_1.scala create mode 100644 tests/pos-macros/i7110c/Test_2.scala create mode 100644 tests/pos-macros/i7110d/Macro_1.scala create mode 100644 tests/pos-macros/i7110d/Test_2.scala create mode 100644 tests/pos-macros/i7110e/Macro_1.scala create mode 100644 tests/pos-macros/i7110e/Test_2.scala create mode 100644 tests/pos-macros/i7110f/Macro_1.scala create mode 100644 tests/pos-macros/i7110f/Test_2.scala create mode 100644 tests/pos/quoted-var.scala create mode 100644 tests/run-staging/abstract-int-quote.scala create mode 100644 tests/run-staging/quote-nested-6.check create mode 100644 tests/run-staging/quote-nested-6.scala create mode 100644 tests/run/i7110.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index e6ca5adc1e5f..e3bd3147cc35 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -42,7 +42,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { Super(qual, if (mixName.isEmpty) untpd.EmptyTypeIdent else untpd.Ident(mixName), mixinClass) def Apply(fn: Tree, args: List[Tree])(implicit ctx: Context): Apply = { - assert(fn.isInstanceOf[RefTree] || fn.isInstanceOf[GenericApply[_]] || fn.isInstanceOf[Inlined]) + assert(fn.isInstanceOf[RefTree] || fn.isInstanceOf[GenericApply[_]] || fn.isInstanceOf[Inlined] || fn.isInstanceOf[tasty.TreePickler.Hole]) ta.assignType(untpd.Apply(fn, args), fn, args) } diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index c2deb60098ca..46fac31370a7 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -61,44 +61,117 @@ object PickledQuotes { }.apply(tp) /** Unpickle the tree contained in the TastyExpr */ - def unpickleExpr(tasty: PickledQuote, args: PickledArgs)(implicit ctx: Context): Tree = { + def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(implicit ctx: Context): Tree = { val tastyBytes = TastyString.unpickle(tasty) - val unpickled = unpickle(tastyBytes, args, isType = false)(ctx.addMode(Mode.ReadPositions)) - /** Force unpickling of the tree, removes the spliced type `@quotedTypeTag type` definitions and dealiases references to `@quotedTypeTag type` */ - val forceAndCleanArtefacts = new TreeMap { - override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { - case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => - assert(rest.forall { case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) }) - transform(expr1) - case tree => super.transform(tree).withType(dealiasTypeTags(tree.tpe)) - } - } - forceAndCleanArtefacts.transform(unpickled) + val unpickled = unpickle(tastyBytes, splices, isType = false)(ctx.addMode(Mode.ReadPositions)) + val Inlined(call, Nil, expnasion) = unpickled + val inlineCtx = inlineContext(call) + val expansion1 = spliceTypes(expnasion, splices)(using inlineCtx) + val expansion2 = spliceTerms(expansion1, splices)(using inlineCtx) + cpy.Inlined(unpickled)(call, Nil, expansion2) } /** Unpickle the tree contained in the TastyType */ def unpickleType(tasty: PickledQuote, args: PickledArgs)(implicit ctx: Context): Tree = { val tastyBytes = TastyString.unpickle(tasty) val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions)) - val tpt = unpickled match { - case Block(aliases, tpt) => - // `@quoteTypeTag type` aliases are not required after unpickling. - // Type definitions are placeholders for type holes in the pickled quote, at this point - // those holes have been filled. As we already dealias al references to them in `dealiasTypeTags` - // there is no need to keep their definitions in the tree. As artifacts of quote reification - // they also do not have a meaningful position in the source. - val aliases1 = aliases.filter(!_.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) - seq(aliases1, tpt) - case tpt => tpt + spliceTypes(unpickled, args) + } + + /** Replace all term holes with the spliced terms */ + private def spliceTerms(tree: Tree, splices: PickledArgs)(using Context): Tree = { + val evaluateHoles = new TreeMap { + override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { + case Hole(isTerm, idx, args) => + val reifiedArgs = args.map { arg => + if (arg.isTerm) (qctx: scala.quoted.QuoteContext) ?=> new TastyTreeExpr(arg, QuoteContext.scopeId) + else new TreeType(arg, QuoteContext.scopeId) + } + if isTerm then + val splice1 = splices(idx).asInstanceOf[Seq[Any] => scala.quoted.QuoteContext ?=> quoted.Expr[?]] + val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContext()) + val filled = PickledQuotes.quotedExprToTree(quotedExpr) + + // We need to make sure a hole is created with the source file of the surrounding context, even if + // it filled with contents a different source file. + if filled.source == ctx.source then filled + else filled.cloneIn(ctx.source).withSpan(tree.span) + else + // Replaces type holes generated by ReifyQuotes (non-spliced types). + // These are types defined in a quote and used at the same level in a nested quote. + val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs) + PickledQuotes.quotedTypeToTree(quotedType) + case tree: Select => + // Retain selected members + val qual = transform(tree.qualifier) + qual.select(tree.symbol).withSpan(tree.span) + + case tree => + if tree.isDef then + tree.symbol.annotations = tree.symbol.annotations.map { + annot => annot.derivedAnnotation(transform(annot.tree)) + } + end if + + val tree1 = super.transform(tree) + tree1.withType(mapAnnots(tree1.tpe)) + } + + // Evaluate holes in type annotations + private val mapAnnots = new TypeMap { + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } + } } - tpt.withType(dealiasTypeTags(tpt.tpe)) + val tree1 = evaluateHoles.transform(tree) + quotePickling.println(i"**** evaluated quote\n$tree1") + tree1 + } + + /** Replace all type holes generated with the spliced types */ + private def spliceTypes(tree: Tree, splices: PickledArgs)(using Context): Tree = { + tree match + case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => + val typeSpliceMap = (stat :: rest).iterator.map { + case tdef: TypeDef => + assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) + val tree = tdef.rhs match + case TypeBoundsTree(_, Hole(_, idx, args), _) => + val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](args) + PickledQuotes.quotedTypeToTree(quotedType) + case TypeBoundsTree(_, tpt, _) => + tpt + (tdef.symbol, tree.tpe) + }.toMap + class ReplaceSplicedTyped extends TypeMap() { + override def apply(tp: Type): Type = { + val tp1 = tp match { + case tp: TypeRef => + typeSpliceMap.get(tp.symbol) match + case Some(t) if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => t + case None => tp + case _ => tp + } + mapOver(tp1) + } + } + val expansion2 = new TreeTypeMap(new ReplaceSplicedTyped).transform(expr1) + quotePickling.println(i"**** typed quote\n${expansion2.show}") + expansion2 + case _ => + tree } // TASTY picklingtests/pos/quoteTest.scala /** Pickle tree into it's TASTY bytes s*/ private def pickle(tree: Tree)(implicit ctx: Context): Array[Byte] = { - quotePickling.println(i"**** pickling quote of \n${tree.show}") + quotePickling.println(i"**** pickling quote of\n$tree") val pickler = new TastyPickler(defn.RootClass) val treePkl = pickler.treePkl treePkl.pickle(tree :: Nil) @@ -122,7 +195,13 @@ object PickledQuotes { unpickler.enter(Set.empty) val tree = unpickler.tree - quotePickling.println(i"**** unpickle quote ${tree.show}") + + // Make sure trees and positions are fully loaded + new TreeTraverser { + def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree) + }.traverse(tree) + + quotePickling.println(i"**** unpickled quote\n$tree") tree } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 42281f023e8e..85f465c8168e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -26,7 +26,8 @@ object TreePickler { override def isTerm: Boolean = isTermHole override def isType: Boolean = !isTermHole override def fallbackToText(printer: Printer): Text = - s"[[$idx|" ~~ printer.toTextGlobal(args, ", ") ~~ "]]" + if isTermHole then s"{{{ $idx |" ~~ printer.toTextGlobal(tpe) ~~ "|" ~~ printer.toTextGlobal(args, ", ") ~~ "}}}" + else s"[[[ $idx |" ~~ printer.toTextGlobal(tpe) ~~ "|" ~~ printer.toTextGlobal(args, ", ") ~~ "]]]" } } @@ -603,6 +604,7 @@ class TreePickler(pickler: TastyPickler) { writeByte(HOLE) withLength { writeNat(idx) + pickleType(tree.tpe, richTypes = true) args.foreach(pickleTree) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1f3d68f0672a..da96e33828ce 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1224,7 +1224,10 @@ class TreeUnpickler(reader: TastyReader, val alias = if currentAddr == end then EmptyTree else readTpt() TypeBoundsTree(lo, hi, alias) case HOLE => - readHole(end, isType = false) + val idx = readNat() + val tpe = readType() + val args = until(end)(readTerm()) + TreePickler.Hole(true, idx, args).withType(tpe) case _ => readPathTerm() } @@ -1257,7 +1260,10 @@ class TreeUnpickler(reader: TastyReader, case HOLE => readByte() val end = readEnd() - readHole(end, isType = true) + val idx = readNat() + val tpe = readType() + val args = until(end)(readTerm()) + TreePickler.Hole(false, idx, args).withType(tpe) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { @@ -1299,35 +1305,6 @@ class TreeUnpickler(reader: TastyReader, owner => new LazyReader(localReader, owner, ctx.mode, ctx.source, op) } - def readHole(end: Addr, isType: Boolean)(implicit ctx: Context): Tree = { - val idx = readNat() - val args = until(end)(readTerm()) - val splice = splices(idx) - def wrap(arg: Tree) = - if (arg.isTerm) (qctx: scala.quoted.QuoteContext) ?=> new TastyTreeExpr(arg, QuoteContext.scopeId) - else new TreeType(arg, QuoteContext.scopeId) - val reifiedArgs = args.map(wrap) - val filled = if (isType) { - val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs) - PickledQuotes.quotedTypeToTree(quotedType) - } - else { - val splice1 = splice.asInstanceOf[Seq[Any] => scala.quoted.QuoteContext ?=> quoted.Expr[?]] - val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContext()) - PickledQuotes.quotedExprToTree(quotedExpr) - } - // We need to make sure a hole is created with the source file of the surrounding context, even if - // it filled with contents a different source file. Otherwise nodes containing holes might end - // up without a position. PositionPickler makes sure that holes always get spans assigned, - // so we can just return the filler tree with the new source and no span here. - if (filled.source == ctx.source) filled - else { - val filled1 = filled.cloneIn(ctx.source) - filled1.span = NoSpan - filled1 - } - } - // ------ Setting positions ------------------------------------------------ /** Pickled span for `addr`. */ diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index ac9acb130ad5..d57e41ed15de 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -331,7 +331,37 @@ class ReifyQuotes extends MacroTransform { */ private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = { val idx = embedded.addTree(body, NoSymbol) - Hole(isTermHole, idx, splices).withType(tpe).asInstanceOf[Hole] + + /** Remove references to local types that will not be defined in this quote */ + def getTypeHoleType(using ctx: Context) = new TypeMap() { + override def apply(tp: Type): Type = tp match + case tp: TypeRef if tp.typeSymbol.isSplice => + apply(tp.dealias) + case tp @ TypeRef(pre, _) if pre == NoPrefix || pre.termSymbol.isLocal => + val hiBound = tp.typeSymbol.info match + case info @ ClassInfo(_, _, classParents, _, _) => classParents.reduce(_ & _) + case info => info.hiBound + apply(hiBound) + case tp => + mapOver(tp) + } + + /** Remove references to local types that will not be defined in this quote */ + def getTermHoleType(using ctx: Context) = new TypeMap() { + override def apply(tp: Type): Type = tp match + case tp @ TypeRef(NoPrefix, _) if capturers.contains(tp.symbol) => + // reference to term with a type defined in outer quote + getTypeHoleType(tp) + case tp @ TermRef(NoPrefix, _) if capturers.contains(tp.symbol) => + // widen term refs to terms defined in outer quote + apply(tp.widenTermRefExpr) + case tp => + mapOver(tp) + } + + val holeType = if isTermHole then getTermHoleType(tpe) else getTypeHoleType(tpe) + + Hole(isTermHole, idx, splices).withType(holeType).asInstanceOf[Hole] } override def transform(tree: Tree)(implicit ctx: Context): Tree = diff --git a/tests/pos-macros/i7853/JsonEncoder_1.scala b/tests/disabled/pos-macros/i7853/JsonEncoder_1.scala similarity index 100% rename from tests/pos-macros/i7853/JsonEncoder_1.scala rename to tests/disabled/pos-macros/i7853/JsonEncoder_1.scala diff --git a/tests/pos-macros/i7853/SummonJsonEncoderTest_2.scala b/tests/disabled/pos-macros/i7853/SummonJsonEncoderTest_2.scala similarity index 100% rename from tests/pos-macros/i7853/SummonJsonEncoderTest_2.scala rename to tests/disabled/pos-macros/i7853/SummonJsonEncoderTest_2.scala diff --git a/tests/pos-macros/i7853/Test_3.scala b/tests/disabled/pos-macros/i7853/Test_3.scala similarity index 100% rename from tests/pos-macros/i7853/Test_3.scala rename to tests/disabled/pos-macros/i7853/Test_3.scala diff --git a/tests/pos-macros/i7110a/Macro_1.scala b/tests/pos-macros/i7110a/Macro_1.scala new file mode 100644 index 000000000000..d35f56ba36ce --- /dev/null +++ b/tests/pos-macros/i7110a/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.quoted._ + +object Macros { + + inline def m[R](sym: Symantics[R]) : R = ${ mImpl[R]('{sym}) } + + def mImpl[R: Type](using qctx: QuoteContext)(sym: Expr[Symantics[R]]): Expr[R] = '{ + $sym.Meth(42) + } +} + +trait Symantics[R] { + def Meth(exp: Int): R + def Meth(): R +} diff --git a/tests/pos-macros/i7110a/Test_2.scala b/tests/pos-macros/i7110a/Test_2.scala new file mode 100644 index 000000000000..c901785a7bf1 --- /dev/null +++ b/tests/pos-macros/i7110a/Test_2.scala @@ -0,0 +1,14 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + + val sym = new Symantics[Int] { + def Meth(exp: Int): Int = exp + def Meth(): Int = 42 + } + + val test = m[Int](sym) + } +} diff --git a/tests/pos-macros/i7110b/Macro_1.scala b/tests/pos-macros/i7110b/Macro_1.scala new file mode 100644 index 000000000000..0587f403c450 --- /dev/null +++ b/tests/pos-macros/i7110b/Macro_1.scala @@ -0,0 +1,16 @@ +import scala.quoted._ + +object Macros { + + inline def m[T](sym: Symantics {type R = T}) : T = ${ mImpl[T]('{sym}) } + + def mImpl[T: Type](using qctx: QuoteContext)(sym: Expr[Symantics { type R = T }]): Expr[T] = '{ + $sym.Meth(42) + } +} + +trait Symantics { + type R + def Meth(exp: Int): R + def Meth(): R +} diff --git a/tests/pos-macros/i7110b/Test_2.scala b/tests/pos-macros/i7110b/Test_2.scala new file mode 100644 index 000000000000..b4ee47b71e14 --- /dev/null +++ b/tests/pos-macros/i7110b/Test_2.scala @@ -0,0 +1,15 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + + val sym = new Symantics { + type R = Int + def Meth(exp: Int): Int = exp + def Meth(): Int = 42 + } + + val test = m(sym) + } +} diff --git a/tests/pos-macros/i7110c/Macro_1.scala b/tests/pos-macros/i7110c/Macro_1.scala new file mode 100644 index 000000000000..b83e3aaf594a --- /dev/null +++ b/tests/pos-macros/i7110c/Macro_1.scala @@ -0,0 +1,14 @@ +import scala.quoted._ + +object Macros { + + inline def m[R](sym: Symantics[R]) : R = ${ mImpl[R]('{sym}) } + + def mImpl[R: Type](using qctx: QuoteContext)(sym: Expr[Symantics[R]]): Expr[R] = '{ + $sym.Meth(42) + } +} + +trait Symantics[R] { + def Meth(exp: Int): R +} diff --git a/tests/pos-macros/i7110c/Test_2.scala b/tests/pos-macros/i7110c/Test_2.scala new file mode 100644 index 000000000000..efb82b88f669 --- /dev/null +++ b/tests/pos-macros/i7110c/Test_2.scala @@ -0,0 +1,16 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + + val sym = new Symantics2 + + val test = m[Int](sym) + } +} + +class Symantics2 extends Symantics[Int] { + def Meth(exp: Int): Int = exp + def Meth(): Int = 42 +} \ No newline at end of file diff --git a/tests/pos-macros/i7110d/Macro_1.scala b/tests/pos-macros/i7110d/Macro_1.scala new file mode 100644 index 000000000000..fe8a8cc002b8 --- /dev/null +++ b/tests/pos-macros/i7110d/Macro_1.scala @@ -0,0 +1,14 @@ +import scala.quoted._ + +object Macros { + + inline def m(sym: Symantics) : Int = ${ mImpl('sym) } + + def mImpl(using qctx: QuoteContext)(sym: Expr[Symantics]): Expr[Int] = '{ + $sym.Meth(42) + } +} + +trait Symantics { + def Meth(exp: Int): Int +} diff --git a/tests/pos-macros/i7110d/Test_2.scala b/tests/pos-macros/i7110d/Test_2.scala new file mode 100644 index 000000000000..5582ec55a133 --- /dev/null +++ b/tests/pos-macros/i7110d/Test_2.scala @@ -0,0 +1,16 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + + val sym = new Symantics2 + + val test = m(sym) + } +} + +class Symantics2 extends Symantics { + def Meth(exp: Int): Int = exp + def Meth(): Int = 42 +} diff --git a/tests/pos-macros/i7110e/Macro_1.scala b/tests/pos-macros/i7110e/Macro_1.scala new file mode 100644 index 000000000000..eab74cdcfc1a --- /dev/null +++ b/tests/pos-macros/i7110e/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.quoted._ + +object Macros { + + inline def m(sym: Symantics, x: Int) : Int = ${ mImpl('sym, 'x) } + + def mImpl(using qctx: QuoteContext)(sym: Expr[Symantics], x: Expr[Int]): Expr[Int] = '{ + $sym.Meth($x) + } +} + +trait Symantics { + def Meth[R](exp: R): Int + def Meth(): Int +} diff --git a/tests/pos-macros/i7110e/Test_2.scala b/tests/pos-macros/i7110e/Test_2.scala new file mode 100644 index 000000000000..881ce247c65c --- /dev/null +++ b/tests/pos-macros/i7110e/Test_2.scala @@ -0,0 +1,14 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + + val sym = new Symantics { + def Meth[R](exp: R): Int = 2 + def Meth(): Int = 42 + } + + val test = m(sym, 3) + } +} diff --git a/tests/pos-macros/i7110f/Macro_1.scala b/tests/pos-macros/i7110f/Macro_1.scala new file mode 100644 index 000000000000..d35f56ba36ce --- /dev/null +++ b/tests/pos-macros/i7110f/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.quoted._ + +object Macros { + + inline def m[R](sym: Symantics[R]) : R = ${ mImpl[R]('{sym}) } + + def mImpl[R: Type](using qctx: QuoteContext)(sym: Expr[Symantics[R]]): Expr[R] = '{ + $sym.Meth(42) + } +} + +trait Symantics[R] { + def Meth(exp: Int): R + def Meth(): R +} diff --git a/tests/pos-macros/i7110f/Test_2.scala b/tests/pos-macros/i7110f/Test_2.scala new file mode 100644 index 000000000000..c901785a7bf1 --- /dev/null +++ b/tests/pos-macros/i7110f/Test_2.scala @@ -0,0 +1,14 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + + val sym = new Symantics[Int] { + def Meth(exp: Int): Int = exp + def Meth(): Int = 42 + } + + val test = m[Int](sym) + } +} diff --git a/tests/pos/quoted-var.scala b/tests/pos/quoted-var.scala new file mode 100644 index 000000000000..7de01c9bb540 --- /dev/null +++ b/tests/pos/quoted-var.scala @@ -0,0 +1,17 @@ +import scala.quoted._ + +class Var[T] + +object Var { + def apply[T: Type, U: Type](init: Expr[T])(body: Var[T] => Expr[U])(using qctx: QuoteContext): Expr[U] = '{ + var x = $init + ${ + body( + new Var[T] { + def get(using qctx: QuoteContext): Expr[T] = 'x + def update(e: Expr[T])(using qctx: QuoteContext): Expr[Unit] = '{ x = $e } + } + ) + } + } +} diff --git a/tests/run-staging/abstract-int-quote.scala b/tests/run-staging/abstract-int-quote.scala new file mode 100644 index 000000000000..00411042cbaf --- /dev/null +++ b/tests/run-staging/abstract-int-quote.scala @@ -0,0 +1,16 @@ +import scala.quoted._ +import scala.quoted.staging._ + +object Test: + + given Toolbox = Toolbox.make(getClass.getClassLoader) + + def main(args: Array[String]): Unit = + def reduce[T: Type](using QuoteContext)(succ: Expr[T] => Expr[T], zero: Expr[T]): Expr[T] = '{ + var z = $zero + ${ succ('z) } + } + def resCode2(using QuoteContext): Expr[Int] = + reduce[Int](x => '{$x + 1}, '{0}) + + println(withQuoteContext(resCode2.show)) diff --git a/tests/run-staging/quote-nested-6.check b/tests/run-staging/quote-nested-6.check new file mode 100644 index 000000000000..05c2bd4eb00c --- /dev/null +++ b/tests/run-staging/quote-nested-6.check @@ -0,0 +1,7 @@ +{ + type T[X] = scala.List[X] + val x: java.lang.String = "foo" + val z: T[scala.Predef.String] = scala.List.apply[java.lang.String](x) + + (x: java.lang.String) +} diff --git a/tests/run-staging/quote-nested-6.scala b/tests/run-staging/quote-nested-6.scala new file mode 100644 index 000000000000..eae08a79328b --- /dev/null +++ b/tests/run-staging/quote-nested-6.scala @@ -0,0 +1,20 @@ +import quoted._ +import scala.quoted.staging._ + +object Test { + given Toolbox = Toolbox.make(getClass.getClassLoader) + + def main(args: Array[String]): Unit = withQuoteContext { + val q = '{ + type T[X] = List[X] + val x = "foo" + ${ + val y = 'x + '{ val z: T[String] = List($y) } + } + x + } + + println(q.show) + } +} diff --git a/tests/run/i7110.scala b/tests/run/i7110.scala new file mode 100644 index 000000000000..203dcfc74e22 --- /dev/null +++ b/tests/run/i7110.scala @@ -0,0 +1,18 @@ + +trait Symantics[R] { + def Meth(exp: Int): R + def Meth(): R +} + +inline def m[R](sym: Symantics[R]) : R = + sym.Meth(42) + +@main def Test: Unit = { + + val sym = new Symantics[Int] { + def Meth(exp: Int): Int = exp + def Meth(): Int = 43 + } + + val test = m[Int](sym) +} From a2961991df04d0844d80908491139fbb32d062cb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 1 Apr 2020 13:55:38 +0200 Subject: [PATCH 2/5] Update TASTy version --- tasty/src/dotty/tools/tasty/TastyFormat.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 9ea3966d0f99..b289ddf75671 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -249,7 +249,7 @@ Standard Section: "Comments" Comment* object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion: Int = 21 + val MajorVersion: Int = 22 val MinorVersion: Int = 0 /** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */ From bfa631a56e24afb6e17243c0f4b1c1b7b84e4807 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 1 Apr 2020 14:12:05 +0200 Subject: [PATCH 3/5] Remove QuoteUnpickler and use DottyUnpickler Without the splices `QuoteUnpickler` is just a copy of the `DottyUnpickler`. --- .../dotc/core/quoted/PickledQuotes.scala | 3 ++- .../dotc/core/quoted/QuoteUnpickler.scala | 27 ------------------- .../dotc/core/tasty/DottyUnpickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +-- 4 files changed, 4 insertions(+), 32 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/core/quoted/QuoteUnpickler.scala diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 46fac31370a7..0e816bc15081 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.tasty.TreePickler.Hole import dotty.tools.dotc.core.tasty.{ PositionPickler, TastyPickler, TastyPrinter } +import dotty.tools.dotc.core.tasty.DottyUnpickler import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode import dotty.tools.dotc.quoted.QuoteContext import dotty.tools.dotc.tastyreflect.{ReflectionImpl, TastyTreeExpr, TreeType} @@ -191,7 +192,7 @@ object PickledQuotes { quotePickling.println(s"**** unpickling quote from TASTY\n${new TastyPrinter(bytes).printContents()}") val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term - val unpickler = new QuoteUnpickler(bytes, splices, mode) + val unpickler = new DottyUnpickler(bytes, mode) unpickler.enter(Set.empty) val tree = unpickler.tree diff --git a/compiler/src/dotty/tools/dotc/core/quoted/QuoteUnpickler.scala b/compiler/src/dotty/tools/dotc/core/quoted/QuoteUnpickler.scala deleted file mode 100644 index b0927b99c3ed..000000000000 --- a/compiler/src/dotty/tools/dotc/core/quoted/QuoteUnpickler.scala +++ /dev/null @@ -1,27 +0,0 @@ -package dotty.tools.dotc.core.quoted - -import dotty.tools.dotc.core.tasty._ -import dotty.tools.dotc.core.tasty.TastyUnpickler.NameTable -import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode - -import dotty.tools.tasty.TastyReader - -object QuoteUnpickler { - class QuotedTreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], splices: Seq[Any]) - extends DottyUnpickler.TreeSectionUnpickler(posUnpickler, None) { - override def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler = - new TreeUnpickler(reader, nameAtRef, posUnpickler, None, splices) - } -} - -/** A class for unpickling quoted Tasty trees and symbols. Comments are never unpickled. - * @param bytes the bytearray containing the Tasty file from which we unpickle - * @param splices splices that will fill the holes in the quote - */ -class QuoteUnpickler(bytes: Array[Byte], splices: Seq[Any], mode: UnpickleMode) extends DottyUnpickler(bytes, mode) { - import DottyUnpickler._ - import QuoteUnpickler._ - - protected override def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler = - new QuotedTreeSectionUnpickler(posUnpicklerOpt, splices) -} diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index 40356da7a40c..5c80e4437e3e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -20,7 +20,7 @@ object DottyUnpickler { class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler]) extends SectionUnpickler[TreeUnpickler](TreePickler.sectionName) { def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler = - new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler, Seq.empty) + new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler) } class PositionsSectionUnpickler extends SectionUnpickler[PositionUnpickler]("Positions") { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index da96e33828ce..515bae0f1504 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -47,13 +47,11 @@ import scala.annotation.internal.sharable * @param reader the reader from which to unpickle * @param posUnpicklerOpt the unpickler for positions, if it exists * @param commentUnpicklerOpt the unpickler for comments, if it exists - * @param splices */ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpicklerOpt: Option[PositionUnpickler], - commentUnpicklerOpt: Option[CommentUnpickler], - splices: Seq[Any]) { + commentUnpicklerOpt: Option[CommentUnpickler]) { import TreeUnpickler._ import tpd._ From daabc817e6c45956c30157ea6545fc6104f17ae8 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 1 Apr 2020 14:14:20 +0200 Subject: [PATCH 4/5] Remove dead code --- .../dotty/tools/dotc/core/quoted/PickledQuotes.scala | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 0e816bc15081..60bce53ce6ec 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -50,17 +50,6 @@ object PickledQuotes { healOwner(tpe1.typeTree) } - private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() { - override def apply(tp: Type): Type = { - val tp1 = tp match { - case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => - tp.symbol.info.hiBound - case _ => tp - } - mapOver(tp1) - } - }.apply(tp) - /** Unpickle the tree contained in the TastyExpr */ def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(implicit ctx: Context): Tree = { val tastyBytes = TastyString.unpickle(tasty) From cebfdd8f5b6249aa1f7d8f4870bf0da7b7349152 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 1 Apr 2020 14:21:21 +0200 Subject: [PATCH 5/5] Update syntax --- compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 60bce53ce6ec..4b4a7d18667a 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -74,7 +74,7 @@ object PickledQuotes { override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { case Hole(isTerm, idx, args) => val reifiedArgs = args.map { arg => - if (arg.isTerm) (qctx: scala.quoted.QuoteContext) ?=> new TastyTreeExpr(arg, QuoteContext.scopeId) + if (arg.isTerm) (using qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg, QuoteContext.scopeId) else new TreeType(arg, QuoteContext.scopeId) } if isTerm then