Skip to content

Commit 807bbba

Browse files
committed
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.
1 parent 100931e commit 807bbba

File tree

10 files changed

+98
-67
lines changed

10 files changed

+98
-67
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -837,8 +837,9 @@ class Definitions {
837837

838838
@tu lazy val TastyReflectionClass: ClassSymbol = requiredClass("scala.tasty.Reflection")
839839

840-
@tu lazy val Unpickler_unpickleExpr: Symbol = requiredMethod("scala.internal.quoted.Unpickler.unpickleExpr")
841-
@tu lazy val Unpickler_unpickleType: Symbol = requiredMethod("scala.internal.quoted.Unpickler.unpickleType")
840+
@tu lazy val PickledExpr_make: Symbol = requiredMethod("scala.internal.quoted.PickledExpr.make")
841+
@tu lazy val PickledType_make: Symbol = requiredMethod("scala.internal.quoted.PickledType.make")
842+
@tu lazy val PickledSplices_make: Symbol = requiredMethod("scala.internal.quoted.PickledSplices.make")
842843

843844
@tu lazy val EqlClass: ClassSymbol = requiredClass("scala.Eql")
844845
def Eql_eqlAny(using Context): TermSymbol = EqlClass.companionModule.requiredMethod(nme.eqlAny)

compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@ import dotty.tools.dotc.core.tasty.DottyUnpickler
1717
import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode
1818
import dotty.tools.dotc.report
1919

20-
import dotty.tools.tasty.TastyString
21-
2220
import scala.reflect.ClassTag
2321

24-
import scala.internal.quoted.Unpickler._
22+
import scala.internal.quoted.PickledExpr
23+
import scala.internal.quoted.PickledSplices
24+
import scala.internal.quoted.PickledType
2525
import scala.quoted.QuoteContext
2626
import scala.collection.mutable
2727

2828
object PickledQuotes {
2929
import tpd._
3030

3131
/** Pickle the tree of the quote into strings */
32-
def pickleQuote(tree: Tree)(using Context): PickledQuote =
32+
def pickleQuote(tree: Tree)(using Context): List[String] =
3333
if (ctx.reporter.hasErrors) Nil
3434
else {
3535
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
3636
val pickled = pickle(tree)
37-
TastyString.pickle(pickled)
37+
scala.internal.quoted.TastyString.pickle(pickled)
3838
}
3939

4040
/** Transform the expression into its fully spliced Tree */
@@ -52,8 +52,7 @@ object PickledQuotes {
5252
}
5353

5454
/** Unpickle the tree contained in the TastyExpr */
55-
def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(using Context): Tree = {
56-
val tastyBytes = TastyString.unpickle(tasty)
55+
def unpickleTerm(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = {
5756
val unpickled = withMode(Mode.ReadPositions)(
5857
unpickle(tastyBytes, splices, isType = false))
5958
val Inlined(call, Nil, expnasion) = unpickled
@@ -64,15 +63,14 @@ object PickledQuotes {
6463
}
6564

6665
/** Unpickle the tree contained in the TastyType */
67-
def unpickleType(tasty: PickledQuote, args: PickledArgs)(using Context): Tree = {
68-
val tastyBytes = TastyString.unpickle(tasty)
66+
def unpickleTypeTree(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = {
6967
val unpickled = withMode(Mode.ReadPositions)(
70-
unpickle(tastyBytes, args, isType = true))
71-
spliceTypes(unpickled, args)
68+
unpickle(tastyBytes, splices, isType = true))
69+
spliceTypes(unpickled, splices)
7270
}
7371

7472
/** Replace all term holes with the spliced terms */
75-
private def spliceTerms(tree: Tree, splices: PickledArgs)(using Context): Tree = {
73+
private def spliceTerms(tree: Tree, splices: PickledSplices)(using Context): Tree = {
7674
val evaluateHoles = new TreeMap {
7775
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match {
7876
case Hole(isTerm, idx, args) =>
@@ -81,8 +79,7 @@ object PickledQuotes {
8179
else new scala.internal.quoted.Type(arg, QuoteContextImpl.scopeId)
8280
}
8381
if isTerm then
84-
val splice1 = splices(idx).asInstanceOf[Seq[Any] => QuoteContext ?=> quoted.Expr[?]]
85-
val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContextImpl())
82+
val quotedExpr = splices.exprSplice(idx)(reifiedArgs)(dotty.tools.dotc.quoted.QuoteContextImpl())
8683
val filled = PickledQuotes.quotedExprToTree(quotedExpr)
8784

8885
// 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 {
9289
else
9390
// Replaces type holes generated by ReifyQuotes (non-spliced types).
9491
// These are types defined in a quote and used at the same level in a nested quote.
95-
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs)
92+
val quotedType = splices.typeSplice(idx)(reifiedArgs)
9693
PickledQuotes.quotedTypeToTree(quotedType)
9794
case tree: Select =>
9895
// Retain selected members
@@ -127,15 +124,15 @@ object PickledQuotes {
127124
}
128125

129126
/** Replace all type holes generated with the spliced types */
130-
private def spliceTypes(tree: Tree, splices: PickledArgs)(using Context): Tree = {
127+
private def spliceTypes(tree: Tree, splices: PickledSplices)(using Context): Tree = {
131128
tree match
132129
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
133130
val typeSpliceMap = (stat :: rest).iterator.map {
134131
case tdef: TypeDef =>
135132
assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot))
136133
val tree = tdef.rhs match
137134
case TypeBoundsTree(_, Hole(_, idx, args), _) =>
138-
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](args)
135+
val quotedType = splices.typeSplice(idx)(args)
139136
PickledQuotes.quotedTypeToTree(quotedType)
140137
case TypeBoundsTree(_, tpt, _) =>
141138
tpt
@@ -181,7 +178,7 @@ object PickledQuotes {
181178
}
182179

183180
/** Unpickle TASTY bytes into it's tree */
184-
private def unpickle(bytes: Array[Byte], splices: Seq[Any], isType: Boolean)(using Context): Tree = {
181+
private def unpickle(bytes: Array[Byte], splices: PickledSplices, isType: Boolean)(using Context): Tree = {
185182
quotePickling.println(s"**** unpickling quote from TASTY\n${new TastyPrinter(bytes).printContents()}")
186183

187184
val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term

compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import dotty.tools.dotc.core.Decorators._
1515
import scala.quoted.QuoteContext
1616
import scala.quoted.show.SyntaxHighlight
1717

18-
import scala.internal.quoted.Unpickler
18+
import scala.internal.quoted.PickledExpr
19+
import scala.internal.quoted.PickledSplices
20+
import scala.internal.quoted.PickledType
1921
import scala.tasty.reflect._
2022

2123
object QuoteContextImpl {
@@ -2607,12 +2609,11 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext:
26072609
private def withDefaultPos[T <: Tree](fn: Context ?=> T): T =
26082610
fn(using ctx.withSource(rootPosition.source)).withSpan(rootPosition.span)
26092611

2612+
def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term =
2613+
PickledQuotes.unpickleTerm(bytes, splices)
26102614

2611-
def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): Term =
2612-
PickledQuotes.unpickleExpr(repr, args)
2613-
2614-
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree =
2615-
PickledQuotes.unpickleType(repr, args)
2615+
def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree =
2616+
PickledQuotes.unpickleTypeTree(bytes, splices)
26162617

26172618
def termMatch(scrutinee: Term, pattern: Term): Option[Tuple] =
26182619
treeMatch(scrutinee, pattern)

compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,12 @@ class ReifyQuotes extends MacroTransform {
163163
}
164164

165165
def pickleAsTasty() = {
166-
val meth = if isType then defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr
166+
val meth = if isType then defn.PickledType_make else defn.PickledExpr_make
167167
val pickledQuoteStrings = liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType)
168+
// TODO: generate an instance of PickledSplices directly instead of passing through a List
168169
val splicesList = liftList(splices, defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.AnyType))
169-
ref(meth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, splicesList)
170+
val pickledSplices = ref(defn.PickledSplices_make).appliedTo(splicesList)
171+
ref(meth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, pickledSplices).select("unpickle".toTermName).appliedToArgs(Nil)
170172
}
171173

172174
def taggedType(sym: Symbol) = ref(defn.InternalQuotedTypeModule).select(sym.name.toTermName)

library/src-bootstrapped/scala/internal/quoted/Unpickler.scala

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package scala.internal.quoted
2+
3+
import scala.quoted._
4+
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface
5+
6+
/** An Expression that is pickled in a */
7+
trait PickledExpr[+T]:
8+
def unpickle(): QuoteContext ?=> Expr[T]
9+
10+
object PickledExpr:
11+
12+
def make[T](pickled: List[String], splices: PickledSplices): PickledExpr[T] =
13+
new PickledExpr[T]:
14+
def unpickle(): QuoteContext ?=> Expr[T] =
15+
val bytes = TastyString.unpickle(pickled)
16+
val qctx = quoteContextWithCompilerInterface(summon[QuoteContext])
17+
val tree = qctx.reflect.unpickleTerm(bytes, splices)
18+
new scala.internal.quoted.Expr(tree, qctx.hashCode).asInstanceOf[Expr[T]]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package scala.internal.quoted
2+
3+
import scala.quoted._
4+
5+
/** Provider of expressions and types that will fill the holes a pickled quote */
6+
trait PickledSplices:
7+
8+
/** Expression that will fill the hole `Hole(<idx> | <args>*)` */
9+
def exprSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */])(qctx: QuoteContext): Expr[Any]
10+
11+
/** Type that will fill the hole `Hole(<idx> | <args>*)` */
12+
def typeSplice(idx: Int)(args: Seq[Any /* Expr[Any] | Type[?] */]): Type[?]
13+
14+
object PickledSplices:
15+
// TODO: generate a more efficient representation
16+
// - avoid creation of lambdas
17+
// - use swich on id
18+
def make(seq: Seq[Seq[Any /* Expr[Any] | Type[?] */] => Any]): PickledSplices =
19+
new PickledSplices:
20+
def exprSplice(idx: Int)(args: Seq[Any])(qctx: QuoteContext): Expr[Any] =
21+
seq(idx)(args).asInstanceOf[QuoteContext => Expr[Any]](qctx)
22+
def typeSplice(idx: Int)(args: Seq[Any]): Type[?] =
23+
seq(idx)(args).asInstanceOf[Type[?]]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package scala.internal.quoted
2+
3+
import scala.quoted._
4+
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface
5+
6+
trait PickledType[T <: AnyKind]:
7+
def unpickle(): QuoteContext ?=> Type[T]
8+
9+
object PickledType:
10+
11+
def make[T <: AnyKind](pickled: List[String], splices: PickledSplices): PickledType[T] =
12+
new PickledType[T]:
13+
def unpickle(): QuoteContext ?=> Type[T] =
14+
val bytes = TastyString.unpickle(pickled)
15+
val qctx = quoteContextWithCompilerInterface(summon[QuoteContext])
16+
val tree = qctx.reflect.unpickleTypeTree(bytes, splices)
17+
new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]]

compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala renamed to library/src/scala/internal/quoted/TastyString.scala

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
1-
package dotty.tools.tasty
1+
package scala.internal.quoted
22

33
import java.io._
44
import java.util.Base64
55
import java.nio.charset.StandardCharsets.UTF_8
66

7-
import scala.internal.quoted.Unpickler.PickledQuote
8-
97
/** Utils for String representation of TASTY */
108
object TastyString {
119

1210
// Max size of a string literal in the bytecode
13-
private final val maxStringSize = 65535
11+
private inline val maxStringSize = 65535
1412

1513
/** Encode TASTY bytes into a List of String */
16-
def pickle(bytes: Array[Byte]): PickledQuote = {
14+
def pickle(bytes: Array[Byte]): List[String] = {
1715
val str = new String(Base64.getEncoder().encode(bytes), UTF_8)
1816
str.toSeq.sliding(maxStringSize, maxStringSize).map(_.unwrap).toList
1917
}
2018

2119
/** Decode the List of Strings into TASTY bytes */
22-
def unpickle(strings: PickledQuote): Array[Byte] = {
20+
def unpickle(strings: List[String]): Array[Byte] = {
2321
val string = new StringBuilder
2422
strings.foreach(string.append)
2523
Base64.getDecoder().decode(string.result().getBytes(UTF_8))

library/src/scala/internal/tasty/CompilerInterface.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package scala.internal.tasty
22

33
import scala.quoted.QuoteContext
44
import scala.tasty.reflect._
5-
import scala.internal.quoted.Unpickler
5+
import scala.internal.quoted.PickledExpr
6+
import scala.internal.quoted.PickledSplices
7+
import scala.internal.quoted.PickledType
68

79
/** Part of the reflection interface that needs to be implemented by the compiler */
810
trait CompilerInterface { self: scala.tasty.Reflection =>
@@ -12,14 +14,14 @@ trait CompilerInterface { self: scala.tasty.Reflection =>
1214
//////////////////////
1315

1416
/** Unpickle `repr` which represents a pickled `Expr` tree,
15-
* replacing splice nodes with `args`
17+
* replacing splice nodes with `holes`
1618
*/
17-
def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): Term
19+
def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term
1820

1921
/** Unpickle `repr` which represents a pickled `Type` tree,
20-
* replacing splice nodes with `args`
22+
* replacing splice nodes with `holes`
2123
*/
22-
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree
24+
def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree
2325

2426
/** Pattern matches the scrutinee against the pattern and returns a tuple
2527
* with the matched holes if successful.

0 commit comments

Comments
 (0)