Skip to content

Commit 4ab3ca2

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 ff3fa46 commit 4ab3ca2

File tree

11 files changed

+120
-62
lines changed

11 files changed

+120
-62
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/core/tasty/TastyString.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@ 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
1311
private final 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))

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

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ import dotty.tools.tasty.TastyString
2121

2222
import scala.reflect.ClassTag
2323

24-
import scala.internal.quoted.Unpickler._
24+
import scala.internal.quoted.PickledExpr
25+
import scala.internal.quoted.PickledSplices
26+
import scala.internal.quoted.PickledType
2527
import scala.quoted.QuoteContext
2628
import scala.collection.mutable
2729

2830
object PickledQuotes {
2931
import tpd._
3032

3133
/** Pickle the tree of the quote into strings */
32-
def pickleQuote(tree: Tree)(using Context): PickledQuote =
34+
def pickleQuote(tree: Tree)(using Context): List[String] =
3335
if (ctx.reporter.hasErrors) Nil
3436
else {
3537
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
@@ -52,8 +54,7 @@ object PickledQuotes {
5254
}
5355

5456
/** Unpickle the tree contained in the TastyExpr */
55-
def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(using Context): Tree = {
56-
val tastyBytes = TastyString.unpickle(tasty)
57+
def unpickleTerm(tastyBytes: Array[Byte], splices: PickledSplices)(using Context): Tree = {
5758
val unpickled = withMode(Mode.ReadPositions)(
5859
unpickle(tastyBytes, splices, isType = false))
5960
val Inlined(call, Nil, expnasion) = unpickled
@@ -64,15 +65,14 @@ object PickledQuotes {
6465
}
6566

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

7474
/** Replace all term holes with the spliced terms */
75-
private def spliceTerms(tree: Tree, splices: PickledArgs)(using Context): Tree = {
75+
private def spliceTerms(tree: Tree, splices: PickledSplices)(using Context): Tree = {
7676
val evaluateHoles = new TreeMap {
7777
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match {
7878
case Hole(isTerm, idx, args) =>
@@ -81,8 +81,7 @@ object PickledQuotes {
8181
else new scala.internal.quoted.Type(arg, QuoteContextImpl.scopeId)
8282
}
8383
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())
84+
val quotedExpr = splices.exprSplice(idx)(reifiedArgs)(dotty.tools.dotc.quoted.QuoteContextImpl())
8685
val filled = PickledQuotes.quotedExprToTree(quotedExpr)
8786

8887
// We need to make sure a hole is created with the source file of the surrounding context, even if
@@ -92,7 +91,7 @@ object PickledQuotes {
9291
else
9392
// Replaces type holes generated by ReifyQuotes (non-spliced types).
9493
// 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)
94+
val quotedType = splices.typeSplice(idx)(reifiedArgs)
9695
PickledQuotes.quotedTypeToTree(quotedType)
9796
case tree: Select =>
9897
// Retain selected members
@@ -127,15 +126,15 @@ object PickledQuotes {
127126
}
128127

129128
/** Replace all type holes generated with the spliced types */
130-
private def spliceTypes(tree: Tree, splices: PickledArgs)(using Context): Tree = {
129+
private def spliceTypes(tree: Tree, splices: PickledSplices)(using Context): Tree = {
131130
tree match
132131
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
133132
val typeSpliceMap = (stat :: rest).iterator.map {
134133
case tdef: TypeDef =>
135134
assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot))
136135
val tree = tdef.rhs match
137136
case TypeBoundsTree(_, Hole(_, idx, args), _) =>
138-
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](args)
137+
val quotedType = splices.typeSplice(idx)(args)
139138
PickledQuotes.quotedTypeToTree(quotedType)
140139
case TypeBoundsTree(_, tpt, _) =>
141140
tpt
@@ -181,7 +180,7 @@ object PickledQuotes {
181180
}
182181

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

187186
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
@@ -16,7 +16,9 @@ import dotty.tools.dotc.typer.Implicits
1616
import scala.quoted.QuoteContext
1717
import scala.quoted.show.SyntaxHighlight
1818

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

2224
object QuoteContextImpl {
@@ -2604,12 +2606,11 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext:
26042606
private def withDefaultPos[T <: Tree](fn: Context ?=> T): T =
26052607
fn(using ctx.withSource(rootPosition.source)).withSpan(rootPosition.span)
26062608

2609+
def unpickleTerm(bytes: Array[Byte], splices: PickledSplices): Term =
2610+
PickledQuotes.unpickleTerm(bytes, splices)
26072611

2608-
def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): Term =
2609-
PickledQuotes.unpickleExpr(repr, args)
2610-
2611-
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree =
2612-
PickledQuotes.unpickleType(repr, args)
2612+
def unpickleTypeTree(bytes: Array[Byte], splices: PickledSplices): TypeTree =
2613+
PickledQuotes.unpickleTypeTree(bytes, splices)
26132614

26142615
def termMatch(scrutinee: Term, pattern: Term): Option[Tuple] =
26152616
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.tasty.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.tasty.unpickleTypeTree(bytes, splices)
17+
new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package scala.internal.quoted
2+
3+
import java.io._
4+
import java.util.Base64
5+
import java.nio.charset.StandardCharsets.UTF_8
6+
7+
/** Utils for String representation of TASTY */
8+
object TastyString {
9+
10+
// Max size of a string literal in the bytecode
11+
private final val maxStringSize = 65535
12+
13+
/** Encode TASTY bytes into a List of String */
14+
def pickle(bytes: Array[Byte]): List[String] = {
15+
val str = new String(Base64.getEncoder().encode(bytes), UTF_8)
16+
str.toSeq.sliding(maxStringSize, maxStringSize).map(_.unwrap).toList
17+
}
18+
19+
/** Decode the List of Strings into TASTY bytes */
20+
def unpickle(strings: List[String]): Array[Byte] = {
21+
val string = new StringBuilder
22+
strings.foreach(string.append)
23+
Base64.getDecoder().decode(string.result().getBytes(UTF_8))
24+
}
25+
}

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)