Skip to content

Commit 36707c7

Browse files
Merge pull request #6963 from dotty-staging/fix-#5376
Fix #5376, fix #5434: Unpickle quotes eagerly
2 parents 95ae77c + 8b8c66a commit 36707c7

File tree

22 files changed

+159
-92
lines changed

22 files changed

+159
-92
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): PickledQuote = {
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: PickledQuote, 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: PickledQuote, 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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
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
86

7+
import scala.runtime.quoted.Unpickler.PickledQuote
8+
99
/** Utils for String representation of TASTY */
1010
object TastyString {
1111

1212
// Max size of a string literal in the bytecode
1313
private final val maxStringSize = 65535
1414

15-
/** Encode TASTY bytes into an Seq of String */
16-
def pickle(bytes: Array[Byte]): Pickled = {
15+
/** Encode TASTY bytes into a List of String */
16+
def pickle(bytes: Array[Byte]): PickledQuote = {
1717
val str = new String(Base64.getEncoder().encode(bytes), UTF_8)
1818
str.sliding(maxStringSize, maxStringSize).toList
1919
}
2020

21-
/** Decode the TASTY String into TASTY bytes */
22-
def unpickle(strings: Pickled): Array[Byte] = {
21+
/** Decode the List of Strings into TASTY bytes */
22+
def unpickle(strings: PickledQuote): Array[Byte] = {
2323
val string = new StringBuilder
2424
strings.foreach(string.append)
2525
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dotty.tools.dotc.quoted
2+
3+
import dotty.tools.dotc.core.Contexts.Context
4+
import dotty.tools.dotc.tastyreflect.ReflectionImpl
5+
6+
object QuoteContext {
7+
8+
def apply() given Context: scala.quoted.QuoteContext =
9+
new scala.quoted.QuoteContext(ReflectionImpl(the[Context]))
10+
11+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import dotty.tools.dotc.parsing.Parsers.Parser
1616
import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType}
1717
import dotty.tools.dotc.util.{SourceFile, SourcePosition, Spans}
1818

19+
import scala.runtime.quoted.Unpickler
1920
import scala.tasty.reflect.CompilerInterface
2021

2122
class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extends CompilerInterface {
@@ -28,6 +29,16 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
2829
def rootPosition: util.SourcePosition =
2930
tastyreflect.MacroExpansion.position.getOrElse(SourcePosition(rootContext.source, Spans.NoSpan))
3031

32+
//
33+
// QUOTE UNPICKLING
34+
//
35+
36+
def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledExprArgs): scala.quoted.Expr[_] =
37+
new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args))
38+
39+
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledTypeArgs): scala.quoted.Type[_] =
40+
new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args))
41+
3142
//
3243
// CONTEXT
3344
//

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/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
*

library/src/scala/quoted/Type.scala

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,9 @@ package quoted {
6868

6969
package internal {
7070
package quoted {
71-
import scala.quoted.Type
72-
import scala.runtime.quoted.Unpickler.Pickled
73-
74-
/** A Type backed by a pickled TASTY tree */
75-
final class TastyType[T](val tasty: Pickled, val args: Seq[Any]) extends Type[T] {
76-
override def toString(): String = s"Type(<pickled tasty>)"
77-
}
7871

7972
/** An Type backed by a tree */
80-
final class TreeType[Tree](val typeTree: Tree) extends Type[Any] {
73+
final class TreeType[Tree](val typeTree: Tree) extends scala.quoted.Type[Any] {
8174
override def toString: String = s"Type(<tasty tree>)"
8275
}
8376

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
1-
package scala.runtime.quoted
1+
package scala.runtime.quoted // TODO move to scala.internal.quoted
22

3-
import scala.internal.quoted.{TastyExpr, TastyType}
43
import scala.quoted.{Expr, QuoteContext, Type}
54

65
/** Provides methods to unpickle `Expr` and `Type` trees. */
76
object Unpickler {
87

9-
/** Representation of pickled trees. For now a List[String],
10-
* but it should be changed to some kind of TASTY bundle.
11-
*/
12-
type Pickled = List[String]
8+
type PickledQuote = List[String]
9+
type PickledExprArgs = Seq[Seq[Any] => ((given QuoteContext => Expr[Any]) | Type[_])]
10+
type PickledTypeArgs = Seq[Seq[Any] => Type[_]]
1311

1412
/** Unpickle `repr` which represents a pickled `Expr` tree,
1513
* replacing splice nodes with `args`
1614
*/
17-
def unpickleExpr[T](repr: Pickled, args: Seq[Seq[Any] => ((given QuoteContext => Expr[Any]) | Type[_])]): given QuoteContext => Expr[T] = new TastyExpr[T](repr, args)
15+
def unpickleExpr[T](repr: PickledQuote, args: PickledExprArgs): given QuoteContext => Expr[T] =
16+
the[QuoteContext].tasty.internal.unpickleExpr(repr, args).asInstanceOf[Expr[T]]
1817

1918
/** Unpickle `repr` which represents a pickled `Type` tree,
2019
* replacing splice nodes with `args`
2120
*/
22-
def unpickleType[T](repr: Pickled, args: Seq[Seq[Any] => Type[_]]): given QuoteContext => Type[T] = new TastyType[T](repr, args)
21+
def unpickleType[T](repr: PickledQuote, args: PickledTypeArgs): given QuoteContext => Type[T] =
22+
the[QuoteContext].tasty.internal.unpickleType(repr, args).asInstanceOf[Type[T]]
23+
2324

2425
}

library/src/scala/tasty/Reflection.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package scala.tasty
22

3+
import scala.quoted.QuoteContext
34
import scala.tasty.reflect._
45

56
class Reflection(private[scala] val internal: CompilerInterface)

library/src/scala/tasty/reflect/Internal.scala renamed to library/src/scala/tasty/reflect/CompilerInterface.scala

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
package scala.tasty.reflect
1+
package scala.tasty.reflect // TODO move to scala.internal.tasty.reflect
22

33
import scala.quoted.QuoteContext
4+
import scala.tasty.Reflection
5+
import scala.runtime.quoted.Unpickler
46

57
/** Tasty reflect abstract types
68
*
@@ -129,6 +131,20 @@ trait CompilerInterface {
129131

130132
def settings: Settings
131133

134+
//
135+
// QUOTE UNPICKLING
136+
//
137+
138+
/** Unpickle `repr` which represents a pickled `Expr` tree,
139+
* replacing splice nodes with `args`
140+
*/
141+
def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledExprArgs): scala.quoted.Expr[_]
142+
143+
/** Unpickle `repr` which represents a pickled `Type` tree,
144+
* replacing splice nodes with `args`
145+
*/
146+
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledTypeArgs): scala.quoted.Type[_]
147+
132148
//
133149
// CONTEXT
134150
//

tests/patmat/i6255b.check

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
2: Pattern Match Exhaustivity: _: Expr[Int]

tests/run-macros/quote-matching-optimize-1.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ Result: ()
1818

1919
Original: scala.List.apply[scala.Int]((1, 2, 3: scala.<repeated>[scala.Int])).map[scala.Int, scala.collection.immutable.List[scala.Int]](((a: scala.Int) => a.*(2)))(scala.collection.immutable.List.canBuildFrom[scala.Int]).map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((b: scala.Int) => b.toString()))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
2020
Optimized: scala.List.apply[scala.Int]((1, 2, 3: scala.<repeated>[scala.Int])).map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((x: scala.Int) => {
21-
val x$5: scala.Int = x.*(2)
22-
x$5.toString()
21+
val x$1: scala.Int = x.*(2)
22+
x$1.toString()
2323
}))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
2424
Result: List(2, 4, 6)
2525

2626
Original: scala.List.apply[scala.Int]((55, 67, 87: scala.<repeated>[scala.Int])).map[scala.Char, scala.collection.immutable.List[scala.Char]](((a: scala.Int) => a.toChar))(scala.collection.immutable.List.canBuildFrom[scala.Char]).map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((b: scala.Char) => b.toString()))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
2727
Optimized: scala.List.apply[scala.Int]((55, 67, 87: scala.<repeated>[scala.Int])).map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((x: scala.Int) => {
28-
val x$10: scala.Char = x.toChar
29-
x$10.toString()
28+
val x$2: scala.Char = x.toChar
29+
x$2.toString()
3030
}))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
3131
Result: List(7, C, W)
3232

tests/run-macros/quote-matching-optimize-2.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ Result: ()
1818

1919
Original: ls.map[scala.Int, scala.collection.immutable.List[scala.Int]](((a: scala.Int) => a.*(2)))(scala.collection.immutable.List.canBuildFrom[scala.Int]).map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((b: scala.Int) => b.toString()))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
2020
Optimized: ls.map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((x: scala.Int) => {
21-
val x$5: scala.Int = x.*(2)
22-
x$5.toString()
21+
val x$1: scala.Int = x.*(2)
22+
x$1.toString()
2323
}))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
2424
Result: List(2, 4, 6)
2525

2626
Original: ls.map[scala.Char, scala.collection.immutable.List[scala.Char]](((a: scala.Int) => a.toChar))(scala.collection.immutable.List.canBuildFrom[scala.Char]).map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((b: scala.Char) => b.toString()))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
2727
Optimized: ls.map[java.lang.String, scala.collection.immutable.List[java.lang.String]](((x: scala.Int) => {
28-
val x$10: scala.Char = x.toChar
29-
x$10.toString()
28+
val x$2: scala.Char = x.toChar
29+
x$2.toString()
3030
}))(scala.collection.immutable.List.canBuildFrom[java.lang.String])
3131
Result: List(, , )
3232

tests/run-with-compiler/i5376.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.+(1).+(2).+(3)

tests/run-with-compiler/i5376.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import scala.quoted._
2+
3+
object Test {
4+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
5+
6+
def main(args: Array[String]): Unit = withQuoteContext {
7+
var e = '{1}
8+
e = '{$e + 1}
9+
e = '{$e + 2}
10+
e = '{$e + 3}
11+
println(e.show)
12+
}
13+
}

tests/run-with-compiler/i5434.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
start
2+
start e
3+
splice
4+
end e
5+
end

0 commit comments

Comments
 (0)