Skip to content

Commit fc1bf94

Browse files
committed
Re-architecture quote pickling
Split cross quote reference handling from pickling Fixes #8100 Fixes #12440
1 parent 31d55e0 commit fc1bf94

File tree

8 files changed

+702
-6
lines changed

8 files changed

+702
-6
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ class Compiler {
5252
List(new Inlining) :: // Inline and execute macros
5353
List(new PostInlining) :: // Add mirror support for inlined code
5454
List(new Staging) :: // Check staging levels and heal staged types
55-
List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures
55+
List(new Splicing) :: // Turn quoted trees into explicit run-time data structures
56+
List(new PickleQuotes2) :: // Turn quoted trees into explicit run-time data structures
57+
// List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures
5658
Nil
5759

5860
/** Phases dealing with the transformation from pickled trees to backend trees */

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ object PickledQuotes {
5555
/** Unpickle the tree contained in the TastyExpr */
5656
def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = {
5757
val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false))
58-
val Inlined(call, Nil, expnasion) = unpickled
58+
val Inlined(call, Nil, expansion0) = unpickled
5959
val inlineCtx = inlineContext(call)
60-
val expansion1 = spliceTypes(expnasion, typeHole, termHole)(using inlineCtx)
60+
val expansion1 = spliceTypes(expansion0, typeHole, termHole)(using inlineCtx)
6161
val expansion2 = spliceTerms(expansion1, typeHole, termHole)(using inlineCtx)
6262
cpy.Inlined(unpickled)(call, Nil, expansion2)
6363
}
@@ -75,7 +75,7 @@ object PickledQuotes {
7575
case Hole(isTerm, idx, args) =>
7676
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
7777
val reifiedArgs = args.map { arg =>
78-
if (arg.isTerm) (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent)
78+
if (arg.isTerm) new ExprImpl(arg, SpliceScope.getCurrent)
7979
else new TypeImpl(arg, SpliceScope.getCurrent)
8080
}
8181
if isTerm then

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

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import Decorators._
6+
import Flags._
7+
import Types._
8+
import Contexts._
9+
import Symbols._
10+
import Constants._
11+
import ast.Trees._
12+
import ast.{TreeTypeMap, untpd}
13+
import util.Spans._
14+
import tasty.TreePickler.Hole
15+
import SymUtils._
16+
import NameKinds._
17+
import dotty.tools.dotc.ast.tpd
18+
import typer.Implicits.SearchFailureType
19+
20+
import scala.collection.mutable
21+
import dotty.tools.dotc.core.Annotations._
22+
import dotty.tools.dotc.core.Names._
23+
import dotty.tools.dotc.core.StdNames._
24+
import dotty.tools.dotc.quoted._
25+
import dotty.tools.dotc.transform.TreeMapWithStages._
26+
import dotty.tools.dotc.typer.Inliner
27+
28+
import scala.annotation.constructorOnly
29+
30+
31+
/** Translates quoted terms and types to `unpickleExpr` or `unpickleType` method calls.
32+
*
33+
* Transforms top level quote
34+
* ```
35+
* '{ ...
36+
* val x1 = ???
37+
* val x2 = ???
38+
* ...
39+
* ${ ... '{ ... x1 ... x2 ...} ... }
40+
* ...
41+
* }
42+
* ```
43+
* to
44+
* ```
45+
* unpickleExpr(
46+
* pickled = [[ // PICKLED TASTY
47+
* ...
48+
* val x1 = ???
49+
* val x2 = ???
50+
* ...
51+
* Hole(<i> | x1, x2)
52+
* ...
53+
* ]],
54+
* typeHole = (idx: Int, args: List[Any]) => idx match {
55+
* case 0 => ...
56+
* },
57+
* termHole = (idx: Int, args: List[Any], quotes: Quotes) => idx match {
58+
* case 0 => ...
59+
* ...
60+
* case <i> =>
61+
* val x1$1 = args(0).asInstanceOf[Expr[T]]
62+
* val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]]
63+
* ...
64+
* { ... '{ ... ${x1$1} ... ${x2$1} ...} ... }
65+
* },
66+
* )
67+
* ```
68+
* and then performs the same transformation on `'{ ... ${x1$1} ... ${x2$1} ...}`.
69+
*
70+
*/
71+
class Splicing extends MacroTransform {
72+
import Splicing._
73+
import tpd._
74+
75+
override def phaseName: String = Splicing.name
76+
77+
override def allowsImplicitSearch: Boolean = true
78+
79+
override def checkPostCondition(tree: Tree)(using Context): Unit =
80+
()
81+
82+
override def run(using Context): Unit =
83+
if (ctx.compilationUnit.needsQuotePickling) super.run(using freshStagingContext)
84+
85+
protected def newTransformer(using Context): Transformer = MyTransformer
86+
87+
object MyTransformer extends Transformer:
88+
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
89+
tree match
90+
case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes))
91+
if fn.symbol == defn.QuotedRuntime_exprQuote =>
92+
val quoteTransformer = new QuoteTransformer
93+
quoteTransformer.transform(tree)
94+
case _ =>
95+
super.transform(tree)
96+
end MyTransformer
97+
98+
class QuoteTransformer extends Transformer:
99+
private val localDefs = mutable.Set.empty[Symbol]
100+
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
101+
tree match
102+
case Apply(fn, List(splicedCode)) if fn.symbol == defn.QuotedRuntime_exprNestedSplice =>
103+
val spliceTransformer = new SpliceTransformer(localDefs.toSet, ctx.owner)
104+
val newSplicedCode1 = spliceTransformer.transformSplice(splicedCode)
105+
val newSplicedCode2 = MyTransformer.transform(newSplicedCode1)
106+
fn.appliedTo(newSplicedCode2)
107+
case tree: DefTree =>
108+
localDefs += tree.symbol
109+
transformAnnotations(tree)
110+
super.transform(tree)
111+
case _ =>
112+
super.transform(tree).withType(mapAnnots(tree.tpe))
113+
114+
private def transformAnnotations(tree: DefTree)(using Context): Unit =
115+
tree.symbol.annotations = tree.symbol.annotations.mapconserve { annot =>
116+
val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol))
117+
if (annot.tree == newAnnotTree) annot
118+
else ConcreteAnnotation(newAnnotTree)
119+
}
120+
private def mapAnnots(using Context) = new TypeMap {
121+
override def apply(tp: Type): Type = {
122+
tp match
123+
case tp @ AnnotatedType(underlying, annot) =>
124+
val underlying1 = this(underlying)
125+
derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree)))
126+
case _ => mapOver(tp)
127+
}
128+
}
129+
130+
end QuoteTransformer
131+
132+
class SpliceTransformer(quoteDefs: Set[Symbol], spliceOwner: Symbol) extends Transformer:
133+
private var refBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)]
134+
private var level = 0
135+
private var quotes: Tree = null
136+
137+
def transformSplice(tree: tpd.Tree)(using Context): tpd.Tree =
138+
val newTree = transform(tree)
139+
val (refs, bindings) = refBindingMap.values.toList.unzip
140+
val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr)
141+
val methType = MethodType(bindingsTypes, newTree.tpe)
142+
val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType)
143+
val ddef = DefDef(meth, List(bindings), newTree.tpe, newTree.changeOwner(ctx.owner, meth))
144+
val fnType = defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe)
145+
val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType)))
146+
closure.select(nme.apply).appliedToArgs(refs)
147+
148+
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
149+
tree match
150+
case tree: RefTree =>
151+
if tree.isTerm then
152+
if quoteDefs.contains(tree.symbol) then
153+
splicedTerm(tree).spliced(tree.tpe.widenTermRefExpr)
154+
else super.transform(tree)
155+
else // tree.isType then
156+
if containsCapturedType(tree.tpe) then
157+
splicedType(tree).select(defn.QuotedType_splice)
158+
else super.transform(tree)
159+
case tree: TypeTree =>
160+
if containsCapturedType(tree.tpe) then
161+
splicedType(tree).select(defn.QuotedType_splice)
162+
else tree
163+
case Assign(lhs: RefTree, rhs) =>
164+
if quoteDefs.contains(lhs.symbol) then transformSplicedAssign(lhs, rhs, tree.tpe)
165+
else super.transform(tree)
166+
case Apply(fn, args) if fn.symbol == defn.QuotedRuntime_exprNestedSplice =>
167+
level -= 1
168+
val newArgs = args.mapConserve(transform)
169+
level += 1
170+
cpy.Apply(tree)(fn, newArgs)
171+
case Apply(sel @ Select(app @ Apply(fn, args),nme.apply), quotesArgs)
172+
if fn.symbol == defn.QuotedRuntime_exprQuote =>
173+
args match
174+
case List(tree: RefTree) if quoteDefs.contains(tree.symbol) =>
175+
splicedTerm(tree)
176+
case _ =>
177+
val oldQuotes = quotes
178+
if level == 0 then quotes = quotesArgs.head
179+
level += 1
180+
val newArgs = args.mapConserve(transform)
181+
level -= 1
182+
quotes = oldQuotes
183+
cpy.Apply(tree)(cpy.Select(sel)(cpy.Apply(app)(fn, newArgs), nme.apply), quotesArgs)
184+
case Apply(TypeApply(_, List(tpt: Ident)), List(quotes))
185+
if tree.symbol == defn.QuotedTypeModule_of && quoteDefs.contains(tpt.symbol) =>
186+
splicedType(tpt)
187+
case _ =>
188+
super.transform(tree)
189+
190+
private def containsCapturedType(tpe: Type)(using Context): Boolean =
191+
tpe match
192+
case tpe @ TypeRef(prefix, _) => quoteDefs.contains(tpe.symbol) || containsCapturedType(prefix)
193+
case tpe @ TermRef(prefix, _) => quoteDefs.contains(tpe.symbol) || containsCapturedType(prefix)
194+
case AppliedType(tycon, args) => containsCapturedType(tycon) || args.exists(containsCapturedType)
195+
case _ => false
196+
197+
private def transformSplicedAssign(lhs: RefTree, rhs: Tree, tpe: Type)(using Context): Tree =
198+
// Make `(x: T) => rhs = x`
199+
val methTpe = MethodType(List(lhs.tpe.widenTermRefExpr), tpe)
200+
val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methTpe)
201+
val closure = Closure(meth, args => Assign(lhs, args.head.head))
202+
203+
val binding = splicedTerm(closure)
204+
// TODO: `${Expr.betaReduce('{$rhsFn.apply(lhs)})}` ?
205+
// Make `$rhsFn.apply(lhs)`
206+
binding.spliced(methTpe.toFunctionType(isJava = false)).select(nme.apply).appliedTo(transform(rhs))
207+
208+
private def splicedTerm(tree: Tree)(using Context): Tree =
209+
val tpe = tree.tpe.widenTermRefExpr match {
210+
case tpw: MethodicType => tpw.toFunctionType(isJava = false)
211+
case tpw => tpw
212+
}
213+
def newBinding = newSymbol(
214+
spliceOwner,
215+
UniqueName.fresh(tree.symbol.name.toTermName).toTermName,
216+
Param,
217+
tpe.exprType,
218+
)
219+
val (_, bindingSym) = refBindingMap.getOrElseUpdate(tree.symbol, (tree.quoted, newBinding))
220+
ref(bindingSym)
221+
222+
private def splicedType(tree: Tree)(using Context): Tree =
223+
val tpe = tree.tpe.widenTermRefExpr
224+
def newBinding = newSymbol(
225+
spliceOwner,
226+
UniqueName.fresh(nme.Type).toTermName,
227+
Param,
228+
defn.QuotedTypeClass.typeRef.appliedTo(tpe),
229+
)
230+
val (_, bindingSym) = refBindingMap.getOrElseUpdate(tree.symbol, (tree.tpe.quoted, newBinding))
231+
ref(bindingSym)
232+
233+
end SpliceTransformer
234+
235+
}
236+
237+
object Splicing {
238+
import tpd._
239+
240+
val name: String = "splicing"
241+
242+
extension (tree: Tree)(using Context)
243+
def spliced(tpe: Type): Tree =
244+
val exprTpe = defn.QuotedExprClass.typeRef.appliedTo(tpe)
245+
val closure =
246+
val methTpe = ContextualMethodType(List(defn.QuotesClass.typeRef), exprTpe)
247+
val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, methTpe)
248+
Closure(meth, _ => tree.changeOwner(ctx.owner, meth))
249+
ref(defn.QuotedRuntime_exprNestedSplice)
250+
.appliedToType(tpe)
251+
.appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it
252+
.appliedTo(closure)
253+
254+
def quoted: Tree =
255+
val tpe = tree.tpe.widenTermRefExpr
256+
ref(defn.QuotedRuntime_exprQuote)
257+
.appliedToType(tpe)
258+
.appliedTo(tree)
259+
.select(nme.apply)
260+
.appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it
261+
end extension
262+
263+
extension (tpe: Type)(using Context)
264+
def exprType: Type =
265+
defn.QuotedExprClass.typeRef.appliedTo(tpe)
266+
def quoted: Tree =
267+
ref(defn.QuotedTypeModule_of)
268+
.appliedToType(tpe)
269+
.appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it
270+
end extension
271+
272+
}

tests/pos-macros/i12440.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted.*
2+
3+
trait Mirror:
4+
type ElemTypes <: Tuple
5+
6+
class Eq:
7+
8+
def test1(using Quotes): Unit = '{
9+
val m: Mirror = ???
10+
${ summonType[m.ElemTypes]; ??? }
11+
${ summonType[List[m.ElemTypes]]; ??? }
12+
}
13+
14+
def test2(using Quotes): Unit = '{
15+
val m: Mirror = ???
16+
type ET = m.ElemTypes
17+
${ summonType[ET]; ??? }
18+
${ summonType[List[ET]]; ??? }
19+
}
20+
21+
def summonType[X](using Type[X]) = ???

tests/pos-macros/i8100.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def f[T: Type](using Quotes) =
1414
${ g[m.E](using Type.of[ME]) }
1515
${ g[ME](using Type.of[m.E]) }
1616
${ g[m.E](using Type.of[m.E]) }
17-
// ${ g[ME] } // FIXME: issue seems to be in PickleQuotes
18-
// ${ g[m.E] } // FIXME: issue seems to be in PickleQuotes
17+
${ g[ME] }
18+
${ g[m.E] }
1919
}
2020

2121
def g[T](using Type[T]) = ???

tests/pos-macros/typetags.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,9 @@ object Test {
77
implicitly[Type[List[Int]]]
88
implicitly[Type[T]]
99
implicitly[Type[List[T]]]
10+
Type.of[Int]
11+
Type.of[List[Int]]
12+
Type.of[T]
13+
Type.of[List[T]]
1014
}
1115
}

tests/run-macros/InlinedTypeOf.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.quoted._
2+
3+
class Sm[T](t: T)
4+
5+
object Foo {
6+
7+
inline def foo[T] = { compiletime.summonInline[Type[T]]; ??? }
8+
9+
def toexpr[T: Type](using Quotes) = foo[Sm[T]]
10+
11+
}
12+

0 commit comments

Comments
 (0)