Skip to content

Commit a635fc1

Browse files
committed
Fix #5376, fix #5434: Unpickle quotes eagerly
1 parent 5bdab50 commit a635fc1

File tree

23 files changed

+178
-91
lines changed

23 files changed

+178
-91
lines changed

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

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ import dotty.tools.dotc.tastyreflect.ReflectionImpl
1919
import scala.internal.quoted._
2020
import scala.reflect.ClassTag
2121

22+
import scala.runtime.quoted.Unpickler._
23+
2224
object PickledQuotes {
2325
import tpd._
2426

2527
/** Pickle the tree of the quote into strings */
26-
def pickleQuote(tree: Tree)(implicit ctx: Context): scala.runtime.quoted.Unpickler.Pickled = {
28+
def pickleQuote(tree: Tree)(implicit ctx: Context): List[String] = {
2729
if (ctx.reporter.hasErrors) Nil
2830
else {
2931
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
@@ -33,33 +35,12 @@ object PickledQuotes {
3335
}
3436

3537
/** Transform the expression into its fully spliced Tree */
36-
def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = expr match {
37-
case expr: TastyExpr[_] =>
38-
val unpickled = unpickleExpr(expr)
39-
/** Force unpickling of the tree, removes the spliced type `@quotedTypeTag type` definitions and dealiases references to `@quotedTypeTag type` */
40-
val forceAndCleanArtefacts = new TreeMap {
41-
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
42-
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
43-
assert(rest.forall { case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) })
44-
transform(expr1)
45-
case tree => super.transform(tree).withType(dealiasTypeTags(tree.tpe))
46-
}
47-
}
48-
forceAndCleanArtefacts.transform(unpickled)
49-
case expr: TastyTreeExpr[Tree] @unchecked => healOwner(expr.tree)
50-
}
38+
def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree =
39+
healOwner(expr.asInstanceOf[TastyTreeExpr[Tree]].tree)
5140

5241
/** Transform the expression into its fully spliced TypeTree */
53-
def quotedTypeToTree(expr: quoted.Type[_])(implicit ctx: Context): Tree = expr match {
54-
case expr: TastyType[_] =>
55-
unpickleType(expr) match {
56-
case Block(aliases, tpt) =>
57-
// `@quoteTypeTag type` aliasses are not required after unpickling
58-
tpt
59-
case tpt => tpt
60-
}
61-
case expr: TreeType[Tree] @unchecked => healOwner(expr.typeTree)
62-
}
42+
def quotedTypeToTree(expr: quoted.Type[_])(implicit ctx: Context): Tree =
43+
healOwner(expr.asInstanceOf[TreeType[Tree]].typeTree)
6344

6445
private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() {
6546
override def apply(tp: Type): Type = {
@@ -72,15 +53,31 @@ object PickledQuotes {
7253
}.apply(tp)
7354

7455
/** Unpickle the tree contained in the TastyExpr */
75-
private def unpickleExpr(expr: TastyExpr[_])(implicit ctx: Context): Tree = {
76-
val tastyBytes = TastyString.unpickle(expr.tasty)
77-
unpickle(tastyBytes, expr.args, isType = false)(ctx.addMode(Mode.ReadPositions))
56+
def unpickleExpr(tasty: PickledExpr, args: PickledExprArgs)(implicit ctx: Context): Tree = {
57+
val tastyBytes = TastyString.unpickle(tasty)
58+
val unpickled = unpickle(tastyBytes, args, isType = false)(ctx.addMode(Mode.ReadPositions))
59+
/** Force unpickling of the tree, removes the spliced type `@quotedTypeTag type` definitions and dealiases references to `@quotedTypeTag type` */
60+
val forceAndCleanArtefacts = new TreeMap {
61+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
62+
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
63+
assert(rest.forall { case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) })
64+
transform(expr1)
65+
case tree => super.transform(tree).withType(dealiasTypeTags(tree.tpe))
66+
}
67+
}
68+
forceAndCleanArtefacts.transform(unpickled)
7869
}
7970

8071
/** Unpickle the tree contained in the TastyType */
81-
private def unpickleType(ttpe: TastyType[_])(implicit ctx: Context): Tree = {
82-
val tastyBytes = TastyString.unpickle(ttpe.tasty)
83-
unpickle(tastyBytes, ttpe.args, isType = true)(ctx.addMode(Mode.ReadPositions))
72+
def unpickleType(tasty: PickledType, args: PickledTypeArgs)(implicit ctx: Context): Tree = {
73+
val tastyBytes = TastyString.unpickle(tasty)
74+
val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions))
75+
unpickled match {
76+
case Block(aliases, tpt) =>
77+
// `@quoteTypeTag type` aliasses are not required after unpickling
78+
tpt
79+
case tpt => tpt
80+
}
8481
}
8582

8683
// TASTY picklingtests/pos/quoteTest.scala

compiler/src/dotty/tools/dotc/core/tasty/TastyString.scala

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package dotty.tools.dotc.core.tasty
22

3-
import scala.runtime.quoted.Unpickler.Pickled
4-
53
import java.io._
64
import java.util.Base64
75
import java.nio.charset.StandardCharsets.UTF_8
@@ -12,14 +10,14 @@ object TastyString {
1210
// Max size of a string literal in the bytecode
1311
private final val maxStringSize = 65535
1412

15-
/** Encode TASTY bytes into an Seq of String */
16-
def pickle(bytes: Array[Byte]): Pickled = {
13+
/** Encode TASTY bytes into a List of String */
14+
def pickle(bytes: Array[Byte]): List[String] = {
1715
val str = new String(Base64.getEncoder().encode(bytes), UTF_8)
1816
str.sliding(maxStringSize, maxStringSize).toList
1917
}
2018

21-
/** Decode the TASTY String into TASTY bytes */
22-
def unpickle(strings: Pickled): Array[Byte] = {
19+
/** Decode the List of Strings into TASTY bytes */
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/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,7 @@ class TreeUnpickler(reader: TastyReader,
12811281
PickledQuotes.quotedTypeToTree(quotedType)
12821282
} else {
12831283
val splice1 = splice.asInstanceOf[Seq[Any] => given scala.quoted.QuoteContext => quoted.Expr[_]]
1284-
val quotedExpr = splice1(reifiedArgs) given new scala.quoted.QuoteContext(tastyreflect.ReflectionImpl(ctx))
1284+
val quotedExpr = splice1(reifiedArgs) given dotty.tools.dotc.quoted.QuoteContext()
12851285
PickledQuotes.quotedExprToTree(quotedExpr)
12861286
}
12871287
// We need to make sure a hole is created with the source file of the surrounding context, even if

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ class QuoteCompiler extends Compiler {
6464
cls.enter(ctx.newDefaultConstructor(cls), EmptyScope)
6565
val meth = ctx.newSymbol(cls, nme.apply, Method, ExprType(defn.AnyType), coord = pos).entered
6666

67-
val quoted = PickledQuotes.quotedExprToTree(exprUnit.exprBuilder.apply(new QuoteContext(ReflectionImpl(ctx))))(ctx.withOwner(meth))
67+
val qctx = dotty.tools.dotc.quoted.QuoteContext()
68+
val quoted = PickledQuotes.quotedExprToTree(exprUnit.exprBuilder.apply(qctx))(ctx.withOwner(meth))
6869

6970
getLiteral(quoted) match {
7071
case Some(value) =>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.core.Contexts.Context
4+
import dotty.tools.dotc.core.Contexts.Context
5+
import dotty.tools.dotc.core.quoted.PickledQuotes
6+
import dotty.tools.dotc.tastyreflect.ReflectionImpl
7+
8+
import scala.quoted.{Expr, Type}
9+
import scala.runtime.quoted.Unpickler._
10+
11+
class QuoteContext private (val tasty: scala.tasty.Reflection) extends scala.quoted.QuoteContext {
12+
13+
def unpickleExpr[T](pickledExpr: PickledExpr, args: PickledExprArgs): given scala.quoted.QuoteContext => Expr[T] = given qctx => {
14+
new scala.internal.quoted.TastyTreeExpr(
15+
PickledQuotes.unpickleExpr(pickledExpr, args) given qctx.tasty.rootContext.asInstanceOf[Context]
16+
).asInstanceOf[Expr[T]]
17+
}
18+
19+
def unpickleType[T](pickledType: PickledType, args: PickledTypeArgs): given scala.quoted.QuoteContext => Type[T] = given qctx => {
20+
new scala.internal.quoted.TreeType(
21+
PickledQuotes.unpickleType(pickledType, args) given qctx.tasty.rootContext.asInstanceOf[Context]
22+
).asInstanceOf[Type[T]]
23+
}
24+
25+
}
26+
27+
28+
object QuoteContext {
29+
30+
def apply() given Context: QuoteContext = apply(ReflectionImpl(the[Context]))
31+
32+
def apply(tastyInstance: scala.tasty.Reflection): QuoteContext { val tasty: tastyInstance.type } =
33+
new QuoteContext(tastyInstance).asInstanceOf[QuoteContext { val tasty: tastyInstance.type }]
34+
35+
}

compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class KernelImpl(val rootContext: core.Contexts.Context) extends Kernel {
2828
def rootPosition: util.SourcePosition =
2929
tastyreflect.MacroExpansion.position.getOrElse(SourcePosition(rootContext.source, Spans.NoSpan))
3030

31+
def QuoteContext_from_Reflection(tastyInstance: scala.tasty.Reflection): scala.quoted.QuoteContext { val tasty: tastyInstance.type } = {
32+
dotty.tools.dotc.quoted.QuoteContext(tastyInstance)
33+
}
34+
3135
//
3236
// CONTEXT
3337
//

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import dotty.tools.repl.AbstractFileClassLoader
2525

2626
import scala.reflect.ClassTag
2727

28+
import dotty.tools.dotc.quoted.QuoteContext
29+
2830
/** Utility class to splice quoted expressions */
2931
object Splicer {
3032
import tpd._
@@ -42,7 +44,7 @@ object Splicer {
4244
try {
4345
// Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree
4446
val interpretedExpr = interpreter.interpret[scala.quoted.QuoteContext => scala.quoted.Expr[Any]](tree)
45-
interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(new scala.quoted.QuoteContext(ReflectionImpl(ctx)))))
47+
interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(QuoteContext())))
4648
}
4749
catch {
4850
case ex: StopInterpretation =>
@@ -261,7 +263,7 @@ object Splicer {
261263
args.toSeq
262264

263265
private def interpretQuoteContext()(implicit env: Env): Object =
264-
new scala.quoted.QuoteContext(ReflectionImpl(ctx))
266+
QuoteContext()
265267

266268
private def interpretedStaticMethodCall(moduleClass: Symbol, fn: Symbol)(implicit env: Env): List[Object] => Object = {
267269
val (inst, clazz) =

library/src-bootstrapped/dotty/internal/StringContextMacro.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ object StringContextMacro {
6666
}
6767
def splitParts(seq: Expr[Seq[String]]) = (seq, seq) match {
6868
case (ExprSeq(p1), ConstSeq(p2)) => Some((p1.toList, p2.toList))
69-
case _ => notStatic
69+
case (_, _) => notStatic
7070
}
7171
strCtxExpr match {
7272
case '{ StringContext($parts: _*) } => splitParts(parts)

library/src-bootstrapped/scala/tasty/reflect/TreeUtils.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ trait TreeUtils
286286
/** Bind the `rhs` to a `val` and use it in `body` */
287287
def let(rhs: Term)(body: Ident => Term): Term = {
288288
import scala.quoted.QuoteContext
289-
given as QuoteContext = new QuoteContext(this)
289+
given as QuoteContext = QuoteContext.from(self)
290290
type T // TODO probably it is better to use the Sealed contruct rather than let the user create their own existential type
291291
implicit val rhsTpe: quoted.Type[T] = rhs.tpe.seal.asInstanceOf[quoted.Type[T]]
292292
val rhsExpr = rhs.seal.cast[T]

library/src/scala/quoted/Expr.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,7 @@ package quoted {
7979
package internal {
8080
package quoted {
8181

82-
import scala.quoted._
83-
84-
/** An Expr backed by a pickled TASTY tree */
85-
final class TastyExpr[+T](val tasty: scala.runtime.quoted.Unpickler.Pickled, val args: Seq[Any]) extends Expr[T] {
86-
override def toString: String = s"Expr(<pickled tasty>)"
87-
}
82+
import scala.quoted.{Expr, QuoteContext}
8883

8984
/** An Expr backed by a tree. Only the current compiler trees are allowed.
9085
*

0 commit comments

Comments
 (0)