From 807bbba48dd69ee98da62966e29440f370f97b44 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 6 Oct 2020 10:30:23 +0200 Subject: [PATCH 1/2] Refactor pickled quotes logic Introduce a PickledExpr, PickledType and PickledSplice abstractions. This will allow the underlying representation of the pickled quote or quote arguments to change in a future version if needed. --- .../dotty/tools/dotc/core/Definitions.scala | 5 +-- .../tools/dotc/quoted/PickledQuotes.scala | 33 +++++++++---------- .../tools/dotc/quoted/QuoteContextImpl.scala | 13 ++++---- .../tools/dotc/transform/ReifyQuotes.scala | 6 ++-- .../scala/internal/quoted/Unpickler.scala | 28 ---------------- .../scala/internal/quoted/PickledExpr.scala | 18 ++++++++++ .../internal/quoted/PickledSplices.scala | 23 +++++++++++++ .../scala/internal/quoted/PickledType.scala | 17 ++++++++++ .../scala/internal/quoted}/TastyString.scala | 10 +++--- .../internal/tasty/CompilerInterface.scala | 12 ++++--- 10 files changed, 98 insertions(+), 67 deletions(-) delete mode 100644 library/src-bootstrapped/scala/internal/quoted/Unpickler.scala create mode 100644 library/src/scala/internal/quoted/PickledExpr.scala create mode 100644 library/src/scala/internal/quoted/PickledSplices.scala create mode 100644 library/src/scala/internal/quoted/PickledType.scala rename {compiler/src/dotty/tools/dotc/core/tasty => library/src/scala/internal/quoted}/TastyString.scala (72%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 497c0115f6b5..d49e6aec1a60 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -837,8 +837,9 @@ class Definitions { @tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection") - @tu lazy val Unpickler_unpickleExpr: Symbol = requiredMethod("scala.internal.quoted.Unpickler.unpickleExpr") - @tu lazy val Unpickler_unpickleType: Symbol = requiredMethod("scala.internal.quoted.Unpickler.unpickleType") + @tu lazy val PickledExpr_make: Symbol = requiredMethod("scala.internal.quoted.PickledExpr.make") + @tu lazy val PickledType_make: Symbol = requiredMethod("scala.internal.quoted.PickledType.make") + @tu lazy val PickledSplices_make: Symbol = requiredMethod("scala.internal.quoted.PickledSplices.make") @tu lazy val EqlClass: ClassSymbol = requiredClass("scala.Eql") def Eql_eqlAny(using Context): TermSymbol = EqlClass.companionModule.requiredMethod(nme.eqlAny) diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index a6aeb0d6326d..0d318ae43b20 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -17,11 +17,11 @@ import dotty.tools.dotc.core.tasty.DottyUnpickler import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode import dotty.tools.dotc.report -import dotty.tools.tasty.TastyString - import scala.reflect.ClassTag -import scala.internal.quoted.Unpickler._ +import scala.internal.quoted.PickledExpr +import scala.internal.quoted.PickledSplices +import scala.internal.quoted.PickledType import scala.quoted.QuoteContext import scala.collection.mutable @@ -29,12 +29,12 @@ object PickledQuotes { import tpd._ /** Pickle the tree of the quote into strings */ - def pickleQuote(tree: Tree)(using Context): PickledQuote = + def pickleQuote(tree: Tree)(using Context): List[String] = if (ctx.reporter.hasErrors) Nil else { assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x` val pickled = pickle(tree) - TastyString.pickle(pickled) + scala.internal.quoted.TastyString.pickle(pickled) } /** Transform the expression into its fully spliced Tree */ @@ -52,8 +52,7 @@ object PickledQuotes { } /** Unpickle the tree contained in the TastyExpr */ - def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(using Context): Tree = { - val tastyBytes = TastyString.unpickle(tasty) + def unpickleTerm(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)( unpickle(tastyBytes, splices, isType = false)) val Inlined(call, Nil, expnasion) = unpickled @@ -64,15 +63,14 @@ object PickledQuotes { } /** Unpickle the tree contained in the TastyType */ - def unpickleType(tasty: PickledQuote, args: PickledArgs)(using Context): Tree = { - val tastyBytes = TastyString.unpickle(tasty) + def unpickleTypeTree(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)( - unpickle(tastyBytes, args, isType = true)) - spliceTypes(unpickled, args) + unpickle(tastyBytes, splices, isType = true)) + spliceTypes(unpickled, splices) } /** Replace all term holes with the spliced terms */ - private def spliceTerms(tree: Tree, splices: PickledArgs)(using Context): Tree = { + private def spliceTerms(tree: Tree, splices: PickledSplices)(using Context): Tree = { val evaluateHoles = new TreeMap { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { case Hole(isTerm, idx, args) => @@ -81,8 +79,7 @@ object PickledQuotes { else new scala.internal.quoted.Type(arg, QuoteContextImpl.scopeId) } if isTerm then - val splice1 = splices(idx).asInstanceOf[Seq[Any] => QuoteContext ?=> quoted.Expr[?]] - val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContextImpl()) + val quotedExpr = splices.exprSplice(idx)(reifiedArgs)(dotty.tools.dotc.quoted.QuoteContextImpl()) val filled = PickledQuotes.quotedExprToTree(quotedExpr) // We need to make sure a hole is created with the source file of the surrounding context, even if @@ -92,7 +89,7 @@ object PickledQuotes { 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) + val quotedType = splices.typeSplice(idx)(reifiedArgs) PickledQuotes.quotedTypeToTree(quotedType) case tree: Select => // Retain selected members @@ -127,7 +124,7 @@ object PickledQuotes { } /** Replace all type holes generated with the spliced types */ - private def spliceTypes(tree: Tree, splices: PickledArgs)(using Context): Tree = { + private def spliceTypes(tree: Tree, splices: PickledSplices)(using Context): Tree = { tree match case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => val typeSpliceMap = (stat :: rest).iterator.map { @@ -135,7 +132,7 @@ object PickledQuotes { 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) + val quotedType = splices.typeSplice(idx)(args) PickledQuotes.quotedTypeToTree(quotedType) case TypeBoundsTree(_, tpt, _) => tpt @@ -181,7 +178,7 @@ object PickledQuotes { } /** Unpickle TASTY bytes into it's tree */ - private def unpickle(bytes: Array[Byte], splices: Seq[Any], isType: Boolean)(using Context): Tree = { + private def unpickle(bytes: Array[Byte], splices: PickledSplices, isType: Boolean)(using Context): Tree = { quotePickling.println(s"**** unpickling quote from TASTY\n${new TastyPrinter(bytes).printContents()}") val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala index a4f60c5a2c58..5226e99232c9 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala @@ -15,7 +15,9 @@ import dotty.tools.dotc.core.Decorators._ import scala.quoted.QuoteContext import scala.quoted.show.SyntaxHighlight -import scala.internal.quoted.Unpickler +import scala.internal.quoted.PickledExpr +import scala.internal.quoted.PickledSplices +import scala.internal.quoted.PickledType import scala.tasty.reflect._ object QuoteContextImpl { @@ -2607,12 +2609,11 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext: private def withDefaultPos[T <: Tree](fn: Context ?=> T): T = fn(using ctx.withSource(rootPosition.source)).withSpan(rootPosition.span) + def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term = + PickledQuotes.unpickleTerm(bytes, splices) - def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): Term = - PickledQuotes.unpickleExpr(repr, args) - - def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree = - PickledQuotes.unpickleType(repr, args) + def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree = + PickledQuotes.unpickleTypeTree(bytes, splices) def termMatch(scrutinee: Term, pattern: Term): Option[Tuple] = treeMatch(scrutinee, pattern) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 7ba7130d62cf..1ca7582a958a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -163,10 +163,12 @@ class ReifyQuotes extends MacroTransform { } def pickleAsTasty() = { - val meth = if isType then defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr + val meth = if isType then defn.PickledType_make else defn.PickledExpr_make val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType) + // TODO: generate an instance of PickledSplices directly instead of passing through a List val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType)) - ref(meth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, splicesList) + val pickledSplices = ref(defn.PickledSplices_make).appliedTo(splicesList) + ref(meth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, pickledSplices).select("unpickle".toTermName).appliedToArgs(Nil) } def taggedType(sym: Symbol) = ref(defn.InternalQuotedTypeModule).select(sym.name.toTermName) diff --git a/library/src-bootstrapped/scala/internal/quoted/Unpickler.scala b/library/src-bootstrapped/scala/internal/quoted/Unpickler.scala deleted file mode 100644 index bbf6912fe646..000000000000 --- a/library/src-bootstrapped/scala/internal/quoted/Unpickler.scala +++ /dev/null @@ -1,28 +0,0 @@ -package scala.internal.quoted - -import scala.quoted.{Expr, QuoteContext, Type} -import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface - -/** Provides methods to unpickle `Expr` and `Type` trees. */ -object Unpickler { - - type PickledQuote = List[String] - type PickledArgs = Seq[Seq[Any] => Any/*(([QCtx <: QuoteContext] =>> QCtx ?=> Expr[Any]) | Type[_])*/] - - /** Unpickle `repr` which represents a pickled `Expr` tree, - * replacing splice nodes with `args` - */ - def unpickleExpr[T](repr: PickledQuote, args: PickledArgs): QuoteContext ?=> Expr[T] = - val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) - val tree = qctx.reflect.unpickleExpr(repr, args) - new scala.internal.quoted.Expr(tree, qctx.hashCode).asInstanceOf[Expr[T]] - - /** Unpickle `repr` which represents a pickled `Type` tree, - * replacing splice nodes with `args` - */ - def unpickleType[T](repr: PickledQuote, args: PickledArgs): QuoteContext ?=> Type[T] = - val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) - val tree = qctx.reflect.unpickleType(repr, args) - new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]] - -} diff --git a/library/src/scala/internal/quoted/PickledExpr.scala b/library/src/scala/internal/quoted/PickledExpr.scala new file mode 100644 index 000000000000..a667d026e4e5 --- /dev/null +++ b/library/src/scala/internal/quoted/PickledExpr.scala @@ -0,0 +1,18 @@ +package scala.internal.quoted + +import scala.quoted._ +import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface + +/** An Expression that is pickled in a */ +trait PickledExpr[+T]: + def unpickle(): QuoteContext ?=> Expr[T] + +object PickledExpr: + + def make[T](pickled: List[String], splices: PickledSplices): PickledExpr[T] = + new PickledExpr[T]: + def unpickle(): QuoteContext ?=> Expr[T] = + val bytes = TastyString.unpickle(pickled) + val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) + val tree = qctx.reflect.unpickleTerm(bytes, splices) + new scala.internal.quoted.Expr(tree, qctx.hashCode).asInstanceOf[Expr[T]] diff --git a/library/src/scala/internal/quoted/PickledSplices.scala b/library/src/scala/internal/quoted/PickledSplices.scala new file mode 100644 index 000000000000..06367ded8052 --- /dev/null +++ b/library/src/scala/internal/quoted/PickledSplices.scala @@ -0,0 +1,23 @@ +package scala.internal.quoted + +import scala.quoted._ + +/** Provider of expressions and types that will fill the holes a pickled quote */ +trait PickledSplices: + + /** Expression that will fill the hole `Hole( | *)` */ + def exprSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */])(qctx: QuoteContext): Expr[Any] + + /** Type that will fill the hole `Hole( | *)` */ + def typeSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */]): Type[?] + +object PickledSplices: + // TODO: generate a more efficient representation + // - avoid creation of lambdas + // - use swich on id + def make(seq: Seq[Seq[Any /* Expr[Any] | Type[?] */] => Any]): PickledSplices = + new PickledSplices: + def exprSplice(idx: Int)(args: Seq[Any])(qctx: QuoteContext): Expr[Any] = + seq(idx)(args).asInstanceOf[QuoteContext => Expr[Any]](qctx) + def typeSplice(idx: Int)(args: Seq[Any]): Type[?] = + seq(idx)(args).asInstanceOf[Type[?]] diff --git a/library/src/scala/internal/quoted/PickledType.scala b/library/src/scala/internal/quoted/PickledType.scala new file mode 100644 index 000000000000..2ea95aece6ae --- /dev/null +++ b/library/src/scala/internal/quoted/PickledType.scala @@ -0,0 +1,17 @@ +package scala.internal.quoted + +import scala.quoted._ +import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface + +trait PickledType[T <: AnyKind]: + def unpickle(): QuoteContext ?=> Type[T] + +object PickledType: + + def make[T <: AnyKind](pickled: List[String], splices: PickledSplices): PickledType[T] = + new PickledType[T]: + def unpickle(): QuoteContext ?=> Type[T] = + val bytes = TastyString.unpickle(pickled) + val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) + val tree = qctx.reflect.unpickleTypeTree(bytes, splices) + new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]] diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala b/library/src/scala/internal/quoted/TastyString.scala similarity index 72% rename from compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala rename to library/src/scala/internal/quoted/TastyString.scala index d32722469ad2..765c22f854a9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala +++ b/library/src/scala/internal/quoted/TastyString.scala @@ -1,25 +1,23 @@ -package dotty.tools.tasty +package scala.internal.quoted import java.io._ import java.util.Base64 import java.nio.charset.StandardCharsets.UTF_8 -import scala.internal.quoted.Unpickler.PickledQuote - /** Utils for String representation of TASTY */ object TastyString { // Max size of a string literal in the bytecode - private final val maxStringSize = 65535 + private inline val maxStringSize = 65535 /** Encode TASTY bytes into a List of String */ - def pickle(bytes: Array[Byte]): PickledQuote = { + def pickle(bytes: Array[Byte]): List[String] = { val str = new String(Base64.getEncoder().encode(bytes), UTF_8) str.toSeq.sliding(maxStringSize, maxStringSize).map(_.unwrap).toList } /** Decode the List of Strings into TASTY bytes */ - def unpickle(strings: PickledQuote): Array[Byte] = { + def unpickle(strings: List[String]): Array[Byte] = { val string = new StringBuilder strings.foreach(string.append) Base64.getDecoder().decode(string.result().getBytes(UTF_8)) diff --git a/library/src/scala/internal/tasty/CompilerInterface.scala b/library/src/scala/internal/tasty/CompilerInterface.scala index 8776e8e377cb..06bbf6f128e7 100644 --- a/library/src/scala/internal/tasty/CompilerInterface.scala +++ b/library/src/scala/internal/tasty/CompilerInterface.scala @@ -2,7 +2,9 @@ package scala.internal.tasty import scala.quoted.QuoteContext import scala.tasty.reflect._ -import scala.internal.quoted.Unpickler +import scala.internal.quoted.PickledExpr +import scala.internal.quoted.PickledSplices +import scala.internal.quoted.PickledType /** Part of the reflection interface that needs to be implemented by the compiler */ trait CompilerInterface { self: scala.tasty.Reflection => @@ -12,14 +14,14 @@ trait CompilerInterface { self: scala.tasty.Reflection => ////////////////////// /** Unpickle `repr` which represents a pickled `Expr` tree, - * replacing splice nodes with `args` + * replacing splice nodes with `holes` */ - def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): Term + def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term /** Unpickle `repr` which represents a pickled `Type` tree, - * replacing splice nodes with `args` + * replacing splice nodes with `holes` */ - def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree + def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree /** Pattern matches the scrutinee against the pattern and returns a tuple * with the matched holes if successful. From b4c10e5b7b8abca94832f9048ce2fdf15d744bbe Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 16 Oct 2020 11:37:53 +0200 Subject: [PATCH 2/2] Use single interface for pickled quotes --- .../dotty/tools/dotc/core/Definitions.scala | 6 +-- .../tools/dotc/quoted/PickledQuotes.scala | 31 +++++++------ .../tools/dotc/quoted/QuoteContextImpl.scala | 12 +++-- .../tools/dotc/transform/ReifyQuotes.scala | 6 +-- .../scala/internal/quoted/PickledExpr.scala | 18 -------- .../scala/internal/quoted/PickledQuote.scala | 45 +++++++++++++++++++ .../internal/quoted/PickledSplices.scala | 23 ---------- .../scala/internal/quoted/PickledType.scala | 17 ------- .../internal/tasty/CompilerInterface.scala | 8 ++-- 9 files changed, 74 insertions(+), 92 deletions(-) delete mode 100644 library/src/scala/internal/quoted/PickledExpr.scala create mode 100644 library/src/scala/internal/quoted/PickledQuote.scala delete mode 100644 library/src/scala/internal/quoted/PickledSplices.scala delete mode 100644 library/src/scala/internal/quoted/PickledType.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index d49e6aec1a60..dbf2cd5ee458 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -837,9 +837,9 @@ class Definitions { @tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection") - @tu lazy val PickledExpr_make: Symbol = requiredMethod("scala.internal.quoted.PickledExpr.make") - @tu lazy val PickledType_make: Symbol = requiredMethod("scala.internal.quoted.PickledType.make") - @tu lazy val PickledSplices_make: Symbol = requiredMethod("scala.internal.quoted.PickledSplices.make") + @tu lazy val PickledQuote_make: Symbol = requiredMethod("scala.internal.quoted.PickledQuote.make") + @tu lazy val PickledQuote_unpickleExpr: Symbol = requiredMethod("scala.internal.quoted.PickledQuote.unpickleExpr") + @tu lazy val PickledQuote_unpickleType: Symbol = requiredMethod("scala.internal.quoted.PickledQuote.unpickleType") @tu lazy val EqlClass: ClassSymbol = requiredClass("scala.Eql") def Eql_eqlAny(using Context): TermSymbol = EqlClass.companionModule.requiredMethod(nme.eqlAny) diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 0d318ae43b20..efdda53babe8 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -19,9 +19,7 @@ import dotty.tools.dotc.report import scala.reflect.ClassTag -import scala.internal.quoted.PickledExpr -import scala.internal.quoted.PickledSplices -import scala.internal.quoted.PickledType +import scala.internal.quoted.PickledQuote import scala.quoted.QuoteContext import scala.collection.mutable @@ -52,25 +50,25 @@ object PickledQuotes { } /** Unpickle the tree contained in the TastyExpr */ - def unpickleTerm(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = { + def unpickleTerm(pickledQuote: PickledQuote)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)( - unpickle(tastyBytes, splices, isType = false)) + unpickle(pickledQuote, isType = false)) val Inlined(call, Nil, expnasion) = unpickled val inlineCtx = inlineContext(call) - val expansion1 = spliceTypes(expnasion, splices)(using inlineCtx) - val expansion2 = spliceTerms(expansion1, splices)(using inlineCtx) + val expansion1 = spliceTypes(expnasion, pickledQuote)(using inlineCtx) + val expansion2 = spliceTerms(expansion1, pickledQuote)(using inlineCtx) cpy.Inlined(unpickled)(call, Nil, expansion2) } /** Unpickle the tree contained in the TastyType */ - def unpickleTypeTree(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = { + def unpickleTypeTree(pickledQuote: PickledQuote)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)( - unpickle(tastyBytes, splices, isType = true)) - spliceTypes(unpickled, splices) + unpickle(pickledQuote, isType = true)) + spliceTypes(unpickled, pickledQuote) } /** Replace all term holes with the spliced terms */ - private def spliceTerms(tree: Tree, splices: PickledSplices)(using Context): Tree = { + private def spliceTerms(tree: Tree, pickledQuote: PickledQuote)(using Context): Tree = { val evaluateHoles = new TreeMap { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { case Hole(isTerm, idx, args) => @@ -79,7 +77,7 @@ object PickledQuotes { else new scala.internal.quoted.Type(arg, QuoteContextImpl.scopeId) } if isTerm then - val quotedExpr = splices.exprSplice(idx)(reifiedArgs)(dotty.tools.dotc.quoted.QuoteContextImpl()) + val quotedExpr = pickledQuote.exprSplice(idx)(reifiedArgs)(dotty.tools.dotc.quoted.QuoteContextImpl()) val filled = PickledQuotes.quotedExprToTree(quotedExpr) // We need to make sure a hole is created with the source file of the surrounding context, even if @@ -89,7 +87,7 @@ object PickledQuotes { 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.typeSplice(idx)(reifiedArgs) + val quotedType = pickledQuote.typeSplice(idx)(reifiedArgs) PickledQuotes.quotedTypeToTree(quotedType) case tree: Select => // Retain selected members @@ -124,7 +122,7 @@ object PickledQuotes { } /** Replace all type holes generated with the spliced types */ - private def spliceTypes(tree: Tree, splices: PickledSplices)(using Context): Tree = { + private def spliceTypes(tree: Tree, pickledQuote: PickledQuote)(using Context): Tree = { tree match case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => val typeSpliceMap = (stat :: rest).iterator.map { @@ -132,7 +130,7 @@ object PickledQuotes { assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) val tree = tdef.rhs match case TypeBoundsTree(_, Hole(_, idx, args), _) => - val quotedType = splices.typeSplice(idx)(args) + val quotedType = pickledQuote.typeSplice(idx)(args) PickledQuotes.quotedTypeToTree(quotedType) case TypeBoundsTree(_, tpt, _) => tpt @@ -178,7 +176,8 @@ object PickledQuotes { } /** Unpickle TASTY bytes into it's tree */ - private def unpickle(bytes: Array[Byte], splices: PickledSplices, isType: Boolean)(using Context): Tree = { + private def unpickle(pickledQuote: PickledQuote, isType: Boolean)(using Context): Tree = { + val bytes = pickledQuote.bytes() quotePickling.println(s"**** unpickling quote from TASTY\n${new TastyPrinter(bytes).printContents()}") val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term diff --git a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala index 5226e99232c9..a3853a6490b7 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala @@ -15,9 +15,7 @@ import dotty.tools.dotc.core.Decorators._ import scala.quoted.QuoteContext import scala.quoted.show.SyntaxHighlight -import scala.internal.quoted.PickledExpr -import scala.internal.quoted.PickledSplices -import scala.internal.quoted.PickledType +import scala.internal.quoted.PickledQuote import scala.tasty.reflect._ object QuoteContextImpl { @@ -2609,11 +2607,11 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext: private def withDefaultPos[T <: Tree](fn: Context ?=> T): T = fn(using ctx.withSource(rootPosition.source)).withSpan(rootPosition.span) - def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term = - PickledQuotes.unpickleTerm(bytes, splices) + def unpickleTerm(pickledQuote: PickledQuote): Term = + PickledQuotes.unpickleTerm(pickledQuote) - def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree = - PickledQuotes.unpickleTypeTree(bytes, splices) + def unpickleTypeTree(pickledQuote: PickledQuote): TypeTree = + PickledQuotes.unpickleTypeTree(pickledQuote) def termMatch(scrutinee: Term, pattern: Term): Option[Tuple] = treeMatch(scrutinee, pattern) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 1ca7582a958a..64d8345eacb1 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -163,12 +163,12 @@ class ReifyQuotes extends MacroTransform { } def pickleAsTasty() = { - val meth = if isType then defn.PickledType_make else defn.PickledExpr_make + val unpickleMeth = if isType then defn.PickledQuote_unpickleType else defn.PickledQuote_unpickleExpr val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType) // TODO: generate an instance of PickledSplices directly instead of passing through a List val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType)) - val pickledSplices = ref(defn.PickledSplices_make).appliedTo(splicesList) - ref(meth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, pickledSplices).select("unpickle".toTermName).appliedToArgs(Nil) + val pickledQuote = ref(defn.PickledQuote_make).appliedTo(pickledQuoteStrings, splicesList) + ref(unpickleMeth).appliedToType(originalTp).appliedTo(pickledQuote) } def taggedType(sym: Symbol) = ref(defn.InternalQuotedTypeModule).select(sym.name.toTermName) diff --git a/library/src/scala/internal/quoted/PickledExpr.scala b/library/src/scala/internal/quoted/PickledExpr.scala deleted file mode 100644 index a667d026e4e5..000000000000 --- a/library/src/scala/internal/quoted/PickledExpr.scala +++ /dev/null @@ -1,18 +0,0 @@ -package scala.internal.quoted - -import scala.quoted._ -import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface - -/** An Expression that is pickled in a */ -trait PickledExpr[+T]: - def unpickle(): QuoteContext ?=> Expr[T] - -object PickledExpr: - - def make[T](pickled: List[String], splices: PickledSplices): PickledExpr[T] = - new PickledExpr[T]: - def unpickle(): QuoteContext ?=> Expr[T] = - val bytes = TastyString.unpickle(pickled) - val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) - val tree = qctx.reflect.unpickleTerm(bytes, splices) - new scala.internal.quoted.Expr(tree, qctx.hashCode).asInstanceOf[Expr[T]] diff --git a/library/src/scala/internal/quoted/PickledQuote.scala b/library/src/scala/internal/quoted/PickledQuote.scala new file mode 100644 index 000000000000..e03d52bfd033 --- /dev/null +++ b/library/src/scala/internal/quoted/PickledQuote.scala @@ -0,0 +1,45 @@ +package scala.internal.quoted + +import scala.quoted._ +import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface + +/** Pickled representation of a quoted expression or type */ +trait PickledQuote: + + /** Bytes of the TASTy containing the quoted expression or type */ + def bytes(): Array[Byte] + + /** Expression that will fill the hole `Hole( | *)` */ + def exprSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */])(qctx: QuoteContext): Expr[Any] + + /** Type that will fill the hole `Hole( | *)` */ + def typeSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */]): Type[?] + +object PickledQuote: + + def unpickleExpr[T](pickledQuote: PickledQuote): QuoteContext ?=> Expr[T] = + val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) + val tree = qctx.reflect.unpickleTerm(pickledQuote) + new scala.internal.quoted.Expr(tree, qctx.hashCode).asInstanceOf[Expr[T]] + + def unpickleType[T](pickledQuote: PickledQuote): QuoteContext ?=> Type[T] = + val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) + val tree = qctx.reflect.unpickleTypeTree(pickledQuote) + new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]] + + /** Create an instance of PickledExpr from encoded tasty and sequence of labmdas to fill holes + * + * @param pickled: Bytes of tasty encoded using TastyString.pickle + * @param seq: Sequence containing all the functions needed to fill the holes of the quote + */ + def make(pickled: List[String], seq: Seq[Seq[Any /* Expr[Any] | Type[?] */] => Any]): PickledQuote = + // TODO: generate a more efficient representation + // - avoid creation of lambdas + // - use swich on id + new PickledQuote: + def bytes(): Array[Byte] = + TastyString.unpickle(pickled) + def exprSplice(idx: Int)(args: Seq[Any])(qctx: QuoteContext): Expr[Any] = + seq(idx)(args).asInstanceOf[QuoteContext => Expr[Any]](qctx) + def typeSplice(idx: Int)(args: Seq[Any]): Type[?] = + seq(idx)(args).asInstanceOf[Type[?]] diff --git a/library/src/scala/internal/quoted/PickledSplices.scala b/library/src/scala/internal/quoted/PickledSplices.scala deleted file mode 100644 index 06367ded8052..000000000000 --- a/library/src/scala/internal/quoted/PickledSplices.scala +++ /dev/null @@ -1,23 +0,0 @@ -package scala.internal.quoted - -import scala.quoted._ - -/** Provider of expressions and types that will fill the holes a pickled quote */ -trait PickledSplices: - - /** Expression that will fill the hole `Hole( | *)` */ - def exprSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */])(qctx: QuoteContext): Expr[Any] - - /** Type that will fill the hole `Hole( | *)` */ - def typeSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */]): Type[?] - -object PickledSplices: - // TODO: generate a more efficient representation - // - avoid creation of lambdas - // - use swich on id - def make(seq: Seq[Seq[Any /* Expr[Any] | Type[?] */] => Any]): PickledSplices = - new PickledSplices: - def exprSplice(idx: Int)(args: Seq[Any])(qctx: QuoteContext): Expr[Any] = - seq(idx)(args).asInstanceOf[QuoteContext => Expr[Any]](qctx) - def typeSplice(idx: Int)(args: Seq[Any]): Type[?] = - seq(idx)(args).asInstanceOf[Type[?]] diff --git a/library/src/scala/internal/quoted/PickledType.scala b/library/src/scala/internal/quoted/PickledType.scala deleted file mode 100644 index 2ea95aece6ae..000000000000 --- a/library/src/scala/internal/quoted/PickledType.scala +++ /dev/null @@ -1,17 +0,0 @@ -package scala.internal.quoted - -import scala.quoted._ -import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface - -trait PickledType[T <: AnyKind]: - def unpickle(): QuoteContext ?=> Type[T] - -object PickledType: - - def make[T <: AnyKind](pickled: List[String], splices: PickledSplices): PickledType[T] = - new PickledType[T]: - def unpickle(): QuoteContext ?=> Type[T] = - val bytes = TastyString.unpickle(pickled) - val qctx = quoteContextWithCompilerInterface(summon[QuoteContext]) - val tree = qctx.reflect.unpickleTypeTree(bytes, splices) - new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]] diff --git a/library/src/scala/internal/tasty/CompilerInterface.scala b/library/src/scala/internal/tasty/CompilerInterface.scala index 06bbf6f128e7..a40321be8a56 100644 --- a/library/src/scala/internal/tasty/CompilerInterface.scala +++ b/library/src/scala/internal/tasty/CompilerInterface.scala @@ -2,9 +2,7 @@ package scala.internal.tasty import scala.quoted.QuoteContext import scala.tasty.reflect._ -import scala.internal.quoted.PickledExpr -import scala.internal.quoted.PickledSplices -import scala.internal.quoted.PickledType +import scala.internal.quoted.PickledQuote /** Part of the reflection interface that needs to be implemented by the compiler */ trait CompilerInterface { self: scala.tasty.Reflection => @@ -16,12 +14,12 @@ trait CompilerInterface { self: scala.tasty.Reflection => /** Unpickle `repr` which represents a pickled `Expr` tree, * replacing splice nodes with `holes` */ - def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term + def unpickleTerm(pickledQuote: PickledQuote): Term /** Unpickle `repr` which represents a pickled `Type` tree, * replacing splice nodes with `holes` */ - def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree + def unpickleTypeTree(pickledQuote: PickledQuote): TypeTree /** Pattern matches the scrutinee against the pattern and returns a tuple * with the matched holes if successful.