diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9c8f9fdab6af..71a3cdc28fd5 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -796,8 +796,10 @@ class Definitions { @tu lazy val QuoteContextClass: ClassSymbol = requiredClass("scala.quoted.QuoteContext") @tu lazy val QuoteContextInternalClass: ClassSymbol = requiredClass("scala.internal.quoted.QuoteContextInternal") - @tu lazy val QuoteContextInternalClass_ExprMatch: Symbol = QuoteContextInternalClass.requiredMethod("ExprMatch") - @tu lazy val QuoteContextInternalClass_TypeMatch: Symbol = QuoteContextInternalClass.requiredMethod("TypeMatch") + @tu lazy val QuoteContextInternal_unpickleExpr: Symbol = QuoteContextInternalClass.requiredMethod("unpickleExpr") + @tu lazy val QuoteContextInternal_unpickleType: Symbol = QuoteContextInternalClass.requiredMethod("unpickleType") + @tu lazy val QuoteContextInternal_ExprMatch: Symbol = QuoteContextInternalClass.requiredMethod("ExprMatch") + @tu lazy val QuoteContextInternal_TypeMatch: Symbol = QuoteContextInternalClass.requiredMethod("TypeMatch") @tu lazy val LiftableModule: Symbol = requiredModule("scala.quoted.Liftable") @tu lazy val LiftableModule_BooleanLiftable: Symbol = LiftableModule.requiredMethod("BooleanLiftable") @@ -831,8 +833,6 @@ class Definitions { @tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection") - @tu lazy val PickledQuote_make: Symbol = requiredMethod("scala.internal.quoted.PickledQuote.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 eeee357f78f8..4b0056b6a2a8 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -19,7 +19,6 @@ import dotty.tools.dotc.report import scala.reflect.ClassTag -import scala.internal.quoted.PickledQuote import scala.quoted.QuoteContext import scala.collection.mutable @@ -34,7 +33,7 @@ object PickledQuotes { else { assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x` val pickled = pickle(tree) - scala.internal.quoted.TastyString.pickle(pickled) + TastyString.pickle(pickled) } /** Transform the expression into its fully spliced Tree */ @@ -52,25 +51,23 @@ object PickledQuotes { } /** Unpickle the tree contained in the TastyExpr */ - def unpickleTerm(pickledQuote: PickledQuote)(using Context): Tree = { - val unpickled = withMode(Mode.ReadPositions)( - unpickle(pickledQuote, isType = false)) + def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?])(using Context): Tree = { + val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false)) val Inlined(call, Nil, expnasion) = unpickled val inlineCtx = inlineContext(call) - val expansion1 = spliceTypes(expnasion, pickledQuote)(using inlineCtx) - val expansion2 = spliceTerms(expansion1, pickledQuote)(using inlineCtx) + val expansion1 = spliceTypes(expnasion, typeHole, termHole)(using inlineCtx) + val expansion2 = spliceTerms(expansion1, typeHole, termHole)(using inlineCtx) cpy.Inlined(unpickled)(call, Nil, expansion2) } /** Unpickle the tree contained in the TastyType */ - def unpickleTypeTree(pickledQuote: PickledQuote)(using Context): Tree = { - val unpickled = withMode(Mode.ReadPositions)( - unpickle(pickledQuote, isType = true)) - spliceTypes(unpickled, pickledQuote) + def unpickleTypeTree(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?])(using Context): Tree = { + val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = true)) + spliceTypes(unpickled, typeHole, termHole) } /** Replace all term holes with the spliced terms */ - private def spliceTerms(tree: Tree, pickledQuote: PickledQuote)(using Context): Tree = { + private def spliceTerms(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?])(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 +76,7 @@ object PickledQuotes { else new scala.quoted.internal.Type(arg, QuoteContextImpl.scopeId) } if isTerm then - val quotedExpr = pickledQuote.exprSplice(idx)(reifiedArgs)(dotty.tools.dotc.quoted.QuoteContextImpl()) + val quotedExpr = termHole(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 +86,7 @@ object PickledQuotes { else // Replaces type holes generated by PickleQuotes (non-spliced types). // These are types defined in a quote and used at the same level in a nested quote. - val quotedType = pickledQuote.typeSplice(idx)(reifiedArgs) + val quotedType = typeHole(idx, reifiedArgs) PickledQuotes.quotedTypeToTree(quotedType) case tree: Select => // Retain selected members @@ -124,7 +121,7 @@ object PickledQuotes { } /** Replace all type holes generated with the spliced types */ - private def spliceTypes(tree: Tree, pickledQuote: PickledQuote)(using Context): Tree = { + private def spliceTypes(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Int], scala.quoted.QuoteContext) => Any)(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 +129,7 @@ object PickledQuotes { assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) val tree = tdef.rhs match case TypeBoundsTree(_, Hole(_, idx, args), _) => - val quotedType = pickledQuote.typeSplice(idx)(args) + val quotedType = typeHole(idx, args) PickledQuotes.quotedTypeToTree(quotedType) case TypeBoundsTree(_, tpt, _) => tpt @@ -178,8 +175,11 @@ object PickledQuotes { } /** Unpickle TASTY bytes into it's tree */ - private def unpickle(pickledQuote: PickledQuote, isType: Boolean)(using Context): Tree = { - val bytes = pickledQuote.bytes() + private def unpickle(pickled: String | List[String], isType: Boolean)(using Context): Tree = { + val bytes = pickled match + case pickled: String => TastyString.unpickle(pickled) + case pickled: List[String] => TastyString.unpickle(pickled) + quotePickling.println(s"**** unpickling quote from TASTY\n${TastyPrinter.show(bytes)}") 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 ff6cbe8d3e35..63021dac1925 100644 --- a/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala @@ -16,7 +16,6 @@ import dotty.tools.dotc.core.Decorators._ import scala.quoted.QuoteContext import dotty.tools.dotc.quoted.printers.{Extractors, SourceCode, SyntaxHighlight} -import scala.internal.quoted.PickledQuote import scala.tasty.reflect._ object QuoteContextImpl { @@ -2622,12 +2621,12 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.intern end reflect - def unpickleExpr[T](pickledQuote: PickledQuote): scala.quoted.Expr[T] = - val tree = PickledQuotes.unpickleTerm(pickledQuote)(using reflect.rootContext) + def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?]): scala.quoted.Expr[T] = + val tree = PickledQuotes.unpickleTerm(pickled, typeHole, termHole)(using reflect.rootContext) new scala.quoted.internal.Expr(tree, hash).asInstanceOf[scala.quoted.Expr[T]] - def unpickleType[T <: AnyKind](pickledQuote: PickledQuote): scala.quoted.Type[T] = - val tree = PickledQuotes.unpickleTypeTree(pickledQuote)(using reflect.rootContext) + def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.QuoteContext) => scala.quoted.Expr[?]): scala.quoted.Type[T] = + val tree = PickledQuotes.unpickleTypeTree(pickled, typeHole, termHole)(using reflect.rootContext) new scala.quoted.internal.Type(tree, hash).asInstanceOf[scala.quoted.Type[T]] object ExprMatch extends ExprMatchModule: diff --git a/library/src/scala/internal/quoted/TastyString.scala b/compiler/src/dotty/tools/dotc/quoted/TastyString.scala similarity index 80% rename from library/src/scala/internal/quoted/TastyString.scala rename to compiler/src/dotty/tools/dotc/quoted/TastyString.scala index 765c22f854a9..677c742c41df 100644 --- a/library/src/scala/internal/quoted/TastyString.scala +++ b/compiler/src/dotty/tools/dotc/quoted/TastyString.scala @@ -1,4 +1,4 @@ -package scala.internal.quoted +package dotty.tools.dotc.quoted import java.io._ import java.util.Base64 @@ -22,4 +22,9 @@ object TastyString { strings.foreach(string.append) Base64.getDecoder().decode(string.result().getBytes(UTF_8)) } + + /** Decode the Strings into TASTY bytes */ + def unpickle(string: String): Array[Byte] = + Base64.getDecoder().decode(string.getBytes(UTF_8)) + } diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 73fb7823f92e..33b28d4435ff 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -28,7 +28,7 @@ import dotty.tools.dotc.typer.Inliner import scala.annotation.constructorOnly -/** Translates quoted terms and types to `unpickle` method calls. +/** Translates quoted terms and types to `unpickleExpr` or `unpickleType` method calls. * * Transforms top level quote * ``` @@ -42,23 +42,27 @@ import scala.annotation.constructorOnly * ``` * to * ``` - * unpickle( - * [[ // PICKLED TASTY + * unpickleExpr( + * pickled = [[ // PICKLED TASTY * ... * val x1 = ??? * val x2 = ??? * ... - * Hole(0 | x1, x2) + * Hole( | x1, x2) * ... * ]], - * List( - * (args: Seq[Any]) => { + * typeHole = (idx: Int, args: List[Any]) => idx match { + * case 0 => ... + * }, + * termHole = (idx: Int, args: List[Any], qctx: QuoteContext) => idx match { + * case 0 => ... + * ... + * case => * val x1$1 = args(0).asInstanceOf[Expr[T]] * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] - * ... + * ... * { ... '{ ... ${x1$1} ... ${x2$1} ...} ... } - * } - * ) + * }, * ) * ``` * and then performs the same transformation on `'{ ... ${x1$1} ... ${x2$1} ...}`. @@ -194,23 +198,66 @@ class PickleQuotes extends MacroTransform { * Generate the code * ```scala * qctx => qctx.asInstanceOf[QuoteContextInternal].[]( - * + * , + * , + * , * ) * ``` * this closure is always applied directly to the actual context and the BetaReduce phase removes it. */ def pickleAsTasty() = { - 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 pickledQuote = ref(defn.PickledQuote_make).appliedTo(pickledQuoteStrings, splicesList) + val pickleQuote = PickledQuotes.pickleQuote(body) + val pickledQuoteStrings = pickleQuote match + case x :: Nil => Literal(Constant(x)) + case xs => liftList(xs.map(x => Literal(Constant(x))), defn.StringType) + + // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices + val (typeSplices, termSplices) = splices.zipWithIndex.partition { case (splice, _) => + splice.tpe match + case defn.FunctionOf(_, res, _, _) => res.typeSymbol == defn.QuotedTypeClass + } + + // This and all closures in typeSplices are removed by the BetaReduce phase + val typeHoles = + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible + else + Lambda( + MethodType( + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), + defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), + args => { + val cases = typeSplices.map { case (splice, idx) => + CaseDef(Literal(Constant(idx)), EmptyTree, splice.select(nme.apply).appliedTo(args(1))) + } + Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + } + ) + + // This and all closures in termSplices are removed by the BetaReduce phase + val termHoles = + if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible + else + Lambda( + MethodType( + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuoteContextClass.typeRef), + defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), + args => { + val cases = termSplices.map { case (splice, idx) => + val defn.FunctionOf(_, defn.FunctionOf(qctxType :: _, _, _, _), _, _) = splice.tpe + val rhs = splice.select(nme.apply).appliedTo(args(1)).select(nme.apply).appliedTo(args(2).asInstance(qctxType)) + CaseDef(Literal(Constant(idx)), EmptyTree, rhs) + } + Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + } + ) + val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass val quotedType = quoteClass.typeRef.appliedTo(originalTp) val lambdaTpe = MethodType(defn.QuoteContextClass.typeRef :: Nil, quotedType) def callUnpickle(ts: List[Tree]) = { val qctx = ts.head.asInstance(defn.QuoteContextInternalClass.typeRef) - val unpickleMethName = if isType then "unpickleType" else "unpickleExpr" - qctx.select(unpickleMethName.toTermName).appliedToType(originalTp).appliedTo(pickledQuote) + val unpickleMeth = if isType then defn.QuoteContextInternal_unpickleType else defn.QuoteContextInternal_unpickleExpr + qctx.select(unpickleMeth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, typeHoles, termHoles) } Lambda(lambdaTpe, callUnpickle).withSpan(body.span) } diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index d8c8255ad17b..e7cd634fd42c 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -463,7 +463,7 @@ trait QuotesAndSplices { if (tree.quoted.isTerm) ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx) else ref(defn.QuotedTypeModule_apply.termRef).appliedToTypeTree(shape).select(nme.apply).appliedTo(qctx) - val matchModule = if tree.quoted.isTerm then defn.QuoteContextInternalClass_ExprMatch else defn.QuoteContextInternalClass_TypeMatch + val matchModule = if tree.quoted.isTerm then defn.QuoteContextInternal_ExprMatch else defn.QuoteContextInternal_TypeMatch val unapplyFun = qctx.asInstance(defn.QuoteContextInternalClass.typeRef).select(matchModule).select(nme.unapply) UnApply( diff --git a/library/src/scala/internal/quoted/PickledQuote.scala b/library/src/scala/internal/quoted/PickledQuote.scala deleted file mode 100644 index 53b45a7e62be..000000000000 --- a/library/src/scala/internal/quoted/PickledQuote.scala +++ /dev/null @@ -1,34 +0,0 @@ -package scala.internal.quoted - -import scala.quoted._ - -/** 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: - - /** 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/QuoteContextInternal.scala b/library/src/scala/internal/quoted/QuoteContextInternal.scala index 33e0a140956b..62e4b62fed6e 100644 --- a/library/src/scala/internal/quoted/QuoteContextInternal.scala +++ b/library/src/scala/internal/quoted/QuoteContextInternal.scala @@ -2,7 +2,6 @@ package scala.internal.quoted import scala.quoted.{QuoteContext, Expr, Type} import scala.tasty.reflect._ -import scala.internal.quoted.PickledQuote /** Part of the QuoteContext interface that needs to be implemented by the compiler but is not visible to users */ trait QuoteContextInternal { self: QuoteContext => @@ -10,12 +9,12 @@ trait QuoteContextInternal { self: QuoteContext => /** Unpickle `repr` which represents a pickled `Expr` tree, * replacing splice nodes with `holes` */ - def unpickleExpr[T](pickledQuote: PickledQuote): scala.quoted.Expr[T] + def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], QuoteContext) => Expr[?]): scala.quoted.Expr[T] /** Unpickle `repr` which represents a pickled `Type` tree, * replacing splice nodes with `holes` */ - def unpickleType[T <: AnyKind](pickledQuote: PickledQuote): scala.quoted.Type[T] + def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], QuoteContext) => Expr[?]): scala.quoted.Type[T] val ExprMatch: ExprMatchModule