Skip to content

Commit 2e92d7a

Browse files
committed
Fix #5376, fix #5434: Unpickle quotes eagerly
1 parent c67b6c9 commit 2e92d7a

File tree

21 files changed

+163
-72
lines changed

21 files changed

+163
-72
lines changed

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

Lines changed: 29 additions & 30 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`
@@ -34,34 +36,15 @@ object PickledQuotes {
3436

3537
/** Transform the expression into its fully spliced Tree */
3638
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)
4939
case expr: TastyTreeExpr[Tree] @unchecked => healOwner(expr.tree)
5040
case expr: FunctionAppliedTo[_] =>
51-
functionAppliedTo(quotedExprToTree(expr.f), expr.args.map(arg => quotedExprToTree(arg(new scala.quoted.QuoteContext(ReflectionImpl(ctx))))).toList)
41+
def qctx = dotty.tools.dotc.quoted.QuoteContext()
42+
functionAppliedTo(quotedExprToTree(expr.f), expr.args.map(arg => quotedExprToTree(arg(qctx))).toList)
5243
}
5344

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

6649
private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() {
6750
override def apply(tp: Type): Type = {
@@ -74,15 +57,31 @@ object PickledQuotes {
7457
}.apply(tp)
7558

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

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

8887
// 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
@@ -27,6 +27,10 @@ class KernelImpl(val rootContext: core.Contexts.Context) extends Kernel {
2727
def rootPosition: util.SourcePosition =
2828
tastyreflect.MacroExpansion.position.getOrElse(SourcePosition(rootContext.source, Spans.NoSpan))
2929

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

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/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
@@ -75,12 +75,7 @@ package quoted {
7575
package internal {
7676
package quoted {
7777

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

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

library/src/scala/quoted/QuoteContext.scala

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package scala.quoted
22

33
import scala.quoted.show.SyntaxHighlight
44

5+
import scala.runtime.quoted.Unpickler._
6+
57
/** Quotation context provided by a macro expansion or in the scope of `scala.quoted.run`.
68
* Used to perform all operations on quoted `Expr` or `Type`.
79
*
@@ -10,7 +12,10 @@ import scala.quoted.show.SyntaxHighlight
1012
*
1113
* @param tasty Typed AST API. Usage: `def f(qctx: QuoteContext) = { import qctx.tasty._; ... }`.
1214
*/
13-
class QuoteContext(val tasty: scala.tasty.Reflection) {
15+
trait QuoteContext {
16+
17+
/** Typed AST API. Usage: `def f(qctx: QuoteContext) = { import qctx.tasty._; ... }`. */
18+
val tasty: scala.tasty.Reflection
1419

1520
def show(expr: Expr[_], syntaxHighlight: SyntaxHighlight): String = {
1621
import tasty._
@@ -46,8 +51,18 @@ class QuoteContext(val tasty: scala.tasty.Reflection) {
4651
tasty.warning(msg, expr.unseal.pos) given rootContext
4752
}
4853

54+
/** Unpickle `pickledExpr` which represents a pickled `Expr` tree
55+
*/
56+
private[scala] def unpickleExpr[T](pickledExpr: PickledExpr, args: PickledExprArgs): given QuoteContext => Expr[T]
57+
58+
/** Unpickle `pickledType` which represents a pickled `Type` tree
59+
*/
60+
private[scala] def unpickleType[T](pickledType: PickledType, args: PickledTypeArgs): given QuoteContext => Type[T]
61+
4962
}
5063

5164
object QuoteContext {
5265
def macroContext: QuoteContext = throw new Exception("Not in inline macro.")
66+
def from(tastyInstance: scala.tasty.Reflection): QuoteContext { val tasty: tastyInstance.type } =
67+
tastyInstance.kernel.QuoteContext_from_Reflection(tastyInstance)
5368
}

0 commit comments

Comments
 (0)