Skip to content

Commit 0bd0fcf

Browse files
Merge pull request #8561 from dotty-staging/fix-splice-overload-2
Fix #7110: Fix splice overload
2 parents 7a28eef + cebfdd8 commit 0bd0fcf

28 files changed

+408
-103
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
4242
Super(qual, if (mixName.isEmpty) untpd.EmptyTypeIdent else untpd.Ident(mixName), mixinClass)
4343

4444
def Apply(fn: Tree, args: List[Tree])(implicit ctx: Context): Apply = {
45-
assert(fn.isInstanceOf[RefTree] || fn.isInstanceOf[GenericApply[_]] || fn.isInstanceOf[Inlined])
45+
assert(fn.isInstanceOf[RefTree] || fn.isInstanceOf[GenericApply[_]] || fn.isInstanceOf[Inlined] || fn.isInstanceOf[tasty.TreePickler.Hole])
4646
ta.assignType(untpd.Apply(fn, args), fn, args)
4747
}
4848

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

Lines changed: 106 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Symbols._
1313
import dotty.tools.dotc.core.Types._
1414
import dotty.tools.dotc.core.tasty.TreePickler.Hole
1515
import dotty.tools.dotc.core.tasty.{ PositionPickler, TastyPickler, TastyPrinter }
16+
import dotty.tools.dotc.core.tasty.DottyUnpickler
1617
import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode
1718
import dotty.tools.dotc.quoted.QuoteContext
1819
import dotty.tools.dotc.tastyreflect.{ReflectionImpl, TastyTreeExpr, TreeType}
@@ -49,56 +50,118 @@ object PickledQuotes {
4950
healOwner(tpe1.typeTree)
5051
}
5152

52-
private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() {
53-
override def apply(tp: Type): Type = {
54-
val tp1 = tp match {
55-
case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
56-
tp.symbol.info.hiBound
57-
case _ => tp
58-
}
59-
mapOver(tp1)
60-
}
61-
}.apply(tp)
62-
6353
/** Unpickle the tree contained in the TastyExpr */
64-
def unpickleExpr(tasty: PickledQuote, args: PickledArgs)(implicit ctx: Context): Tree = {
54+
def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(implicit ctx: Context): Tree = {
6555
val tastyBytes = TastyString.unpickle(tasty)
66-
val unpickled = unpickle(tastyBytes, args, isType = false)(ctx.addMode(Mode.ReadPositions))
67-
/** Force unpickling of the tree, removes the spliced type `@quotedTypeTag type` definitions and dealiases references to `@quotedTypeTag type` */
68-
val forceAndCleanArtefacts = new TreeMap {
69-
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
70-
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
71-
assert(rest.forall { case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) })
72-
transform(expr1)
73-
case tree => super.transform(tree).withType(dealiasTypeTags(tree.tpe))
74-
}
75-
}
76-
forceAndCleanArtefacts.transform(unpickled)
56+
val unpickled = unpickle(tastyBytes, splices, isType = false)(ctx.addMode(Mode.ReadPositions))
57+
val Inlined(call, Nil, expnasion) = unpickled
58+
val inlineCtx = inlineContext(call)
59+
val expansion1 = spliceTypes(expnasion, splices)(using inlineCtx)
60+
val expansion2 = spliceTerms(expansion1, splices)(using inlineCtx)
61+
cpy.Inlined(unpickled)(call, Nil, expansion2)
7762
}
7863

7964
/** Unpickle the tree contained in the TastyType */
8065
def unpickleType(tasty: PickledQuote, args: PickledArgs)(implicit ctx: Context): Tree = {
8166
val tastyBytes = TastyString.unpickle(tasty)
8267
val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions))
83-
val tpt = unpickled match {
84-
case Block(aliases, tpt) =>
85-
// `@quoteTypeTag type` aliases are not required after unpickling.
86-
// Type definitions are placeholders for type holes in the pickled quote, at this point
87-
// those holes have been filled. As we already dealias al references to them in `dealiasTypeTags`
88-
// there is no need to keep their definitions in the tree. As artifacts of quote reification
89-
// they also do not have a meaningful position in the source.
90-
val aliases1 = aliases.filter(!_.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot))
91-
seq(aliases1, tpt)
92-
case tpt => tpt
68+
spliceTypes(unpickled, args)
69+
}
70+
71+
/** Replace all term holes with the spliced terms */
72+
private def spliceTerms(tree: Tree, splices: PickledArgs)(using Context): Tree = {
73+
val evaluateHoles = new TreeMap {
74+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
75+
case Hole(isTerm, idx, args) =>
76+
val reifiedArgs = args.map { arg =>
77+
if (arg.isTerm) (using qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg, QuoteContext.scopeId)
78+
else new TreeType(arg, QuoteContext.scopeId)
79+
}
80+
if isTerm then
81+
val splice1 = splices(idx).asInstanceOf[Seq[Any] => scala.quoted.QuoteContext ?=> quoted.Expr[?]]
82+
val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContext())
83+
val filled = PickledQuotes.quotedExprToTree(quotedExpr)
84+
85+
// We need to make sure a hole is created with the source file of the surrounding context, even if
86+
// it filled with contents a different source file.
87+
if filled.source == ctx.source then filled
88+
else filled.cloneIn(ctx.source).withSpan(tree.span)
89+
else
90+
// Replaces type holes generated by ReifyQuotes (non-spliced types).
91+
// These are types defined in a quote and used at the same level in a nested quote.
92+
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs)
93+
PickledQuotes.quotedTypeToTree(quotedType)
94+
case tree: Select =>
95+
// Retain selected members
96+
val qual = transform(tree.qualifier)
97+
qual.select(tree.symbol).withSpan(tree.span)
98+
99+
case tree =>
100+
if tree.isDef then
101+
tree.symbol.annotations = tree.symbol.annotations.map {
102+
annot => annot.derivedAnnotation(transform(annot.tree))
103+
}
104+
end if
105+
106+
val tree1 = super.transform(tree)
107+
tree1.withType(mapAnnots(tree1.tpe))
108+
}
109+
110+
// Evaluate holes in type annotations
111+
private val mapAnnots = new TypeMap {
112+
override def apply(tp: Type): Type = {
113+
tp match
114+
case tp @ AnnotatedType(underlying, annot) =>
115+
val underlying1 = this(underlying)
116+
derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree)))
117+
case _ => mapOver(tp)
118+
}
119+
}
93120
}
94-
tpt.withType(dealiasTypeTags(tpt.tpe))
121+
val tree1 = evaluateHoles.transform(tree)
122+
quotePickling.println(i"**** evaluated quote\n$tree1")
123+
tree1
124+
}
125+
126+
/** Replace all type holes generated with the spliced types */
127+
private def spliceTypes(tree: Tree, splices: PickledArgs)(using Context): Tree = {
128+
tree match
129+
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) =>
130+
val typeSpliceMap = (stat :: rest).iterator.map {
131+
case tdef: TypeDef =>
132+
assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot))
133+
val tree = tdef.rhs match
134+
case TypeBoundsTree(_, Hole(_, idx, args), _) =>
135+
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](args)
136+
PickledQuotes.quotedTypeToTree(quotedType)
137+
case TypeBoundsTree(_, tpt, _) =>
138+
tpt
139+
(tdef.symbol, tree.tpe)
140+
}.toMap
141+
class ReplaceSplicedTyped extends TypeMap() {
142+
override def apply(tp: Type): Type = {
143+
val tp1 = tp match {
144+
case tp: TypeRef =>
145+
typeSpliceMap.get(tp.symbol) match
146+
case Some(t) if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => t
147+
case None => tp
148+
case _ => tp
149+
}
150+
mapOver(tp1)
151+
}
152+
}
153+
val expansion2 = new TreeTypeMap(new ReplaceSplicedTyped).transform(expr1)
154+
quotePickling.println(i"**** typed quote\n${expansion2.show}")
155+
expansion2
156+
case _ =>
157+
tree
95158
}
96159

97160
// TASTY picklingtests/pos/quoteTest.scala
98161

99162
/** Pickle tree into it's TASTY bytes s*/
100163
private def pickle(tree: Tree)(implicit ctx: Context): Array[Byte] = {
101-
quotePickling.println(i"**** pickling quote of \n${tree.show}")
164+
quotePickling.println(i"**** pickling quote of\n$tree")
102165
val pickler = new TastyPickler(defn.RootClass)
103166
val treePkl = pickler.treePkl
104167
treePkl.pickle(tree :: Nil)
@@ -118,11 +181,17 @@ object PickledQuotes {
118181
quotePickling.println(s"**** unpickling quote from TASTY\n${new TastyPrinter(bytes).printContents()}")
119182

120183
val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term
121-
val unpickler = new QuoteUnpickler(bytes, splices, mode)
184+
val unpickler = new DottyUnpickler(bytes, mode)
122185
unpickler.enter(Set.empty)
123186

124187
val tree = unpickler.tree
125-
quotePickling.println(i"**** unpickle quote ${tree.show}")
188+
189+
// Make sure trees and positions are fully loaded
190+
new TreeTraverser {
191+
def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree)
192+
}.traverse(tree)
193+
194+
quotePickling.println(i"**** unpickled quote\n$tree")
126195
tree
127196
}
128197

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

Lines changed: 0 additions & 27 deletions
This file was deleted.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object DottyUnpickler {
2020
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler])
2121
extends SectionUnpickler[TreeUnpickler](TreePickler.sectionName) {
2222
def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler =
23-
new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler, Seq.empty)
23+
new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler)
2424
}
2525

2626
class PositionsSectionUnpickler extends SectionUnpickler[PositionUnpickler]("Positions") {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ object TreePickler {
2626
override def isTerm: Boolean = isTermHole
2727
override def isType: Boolean = !isTermHole
2828
override def fallbackToText(printer: Printer): Text =
29-
s"[[$idx|" ~~ printer.toTextGlobal(args, ", ") ~~ "]]"
29+
if isTermHole then s"{{{ $idx |" ~~ printer.toTextGlobal(tpe) ~~ "|" ~~ printer.toTextGlobal(args, ", ") ~~ "}}}"
30+
else s"[[[ $idx |" ~~ printer.toTextGlobal(tpe) ~~ "|" ~~ printer.toTextGlobal(args, ", ") ~~ "]]]"
3031
}
3132
}
3233

@@ -603,6 +604,7 @@ class TreePickler(pickler: TastyPickler) {
603604
writeByte(HOLE)
604605
withLength {
605606
writeNat(idx)
607+
pickleType(tree.tpe, richTypes = true)
606608
args.foreach(pickleTree)
607609
}
608610
}

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

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,11 @@ import scala.annotation.internal.sharable
4747
* @param reader the reader from which to unpickle
4848
* @param posUnpicklerOpt the unpickler for positions, if it exists
4949
* @param commentUnpicklerOpt the unpickler for comments, if it exists
50-
* @param splices
5150
*/
5251
class TreeUnpickler(reader: TastyReader,
5352
nameAtRef: NameRef => TermName,
5453
posUnpicklerOpt: Option[PositionUnpickler],
55-
commentUnpicklerOpt: Option[CommentUnpickler],
56-
splices: Seq[Any]) {
54+
commentUnpicklerOpt: Option[CommentUnpickler]) {
5755
import TreeUnpickler._
5856
import tpd._
5957

@@ -1224,7 +1222,10 @@ class TreeUnpickler(reader: TastyReader,
12241222
val alias = if currentAddr == end then EmptyTree else readTpt()
12251223
TypeBoundsTree(lo, hi, alias)
12261224
case HOLE =>
1227-
readHole(end, isType = false)
1225+
val idx = readNat()
1226+
val tpe = readType()
1227+
val args = until(end)(readTerm())
1228+
TreePickler.Hole(true, idx, args).withType(tpe)
12281229
case _ =>
12291230
readPathTerm()
12301231
}
@@ -1257,7 +1258,10 @@ class TreeUnpickler(reader: TastyReader,
12571258
case HOLE =>
12581259
readByte()
12591260
val end = readEnd()
1260-
readHole(end, isType = true)
1261+
val idx = readNat()
1262+
val tpe = readType()
1263+
val args = until(end)(readTerm())
1264+
TreePickler.Hole(false, idx, args).withType(tpe)
12611265
case _ =>
12621266
if (isTypeTreeTag(nextByte)) readTerm()
12631267
else {
@@ -1299,35 +1303,6 @@ class TreeUnpickler(reader: TastyReader,
12991303
owner => new LazyReader(localReader, owner, ctx.mode, ctx.source, op)
13001304
}
13011305

1302-
def readHole(end: Addr, isType: Boolean)(implicit ctx: Context): Tree = {
1303-
val idx = readNat()
1304-
val args = until(end)(readTerm())
1305-
val splice = splices(idx)
1306-
def wrap(arg: Tree) =
1307-
if (arg.isTerm) (qctx: scala.quoted.QuoteContext) ?=> new TastyTreeExpr(arg, QuoteContext.scopeId)
1308-
else new TreeType(arg, QuoteContext.scopeId)
1309-
val reifiedArgs = args.map(wrap)
1310-
val filled = if (isType) {
1311-
val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs)
1312-
PickledQuotes.quotedTypeToTree(quotedType)
1313-
}
1314-
else {
1315-
val splice1 = splice.asInstanceOf[Seq[Any] => scala.quoted.QuoteContext ?=> quoted.Expr[?]]
1316-
val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContext())
1317-
PickledQuotes.quotedExprToTree(quotedExpr)
1318-
}
1319-
// We need to make sure a hole is created with the source file of the surrounding context, even if
1320-
// it filled with contents a different source file. Otherwise nodes containing holes might end
1321-
// up without a position. PositionPickler makes sure that holes always get spans assigned,
1322-
// so we can just return the filler tree with the new source and no span here.
1323-
if (filled.source == ctx.source) filled
1324-
else {
1325-
val filled1 = filled.cloneIn(ctx.source)
1326-
filled1.span = NoSpan
1327-
filled1
1328-
}
1329-
}
1330-
13311306
// ------ Setting positions ------------------------------------------------
13321307

13331308
/** Pickled span for `addr`. */

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,37 @@ class ReifyQuotes extends MacroTransform {
331331
*/
332332
private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = {
333333
val idx = embedded.addTree(body, NoSymbol)
334-
Hole(isTermHole, idx, splices).withType(tpe).asInstanceOf[Hole]
334+
335+
/** Remove references to local types that will not be defined in this quote */
336+
def getTypeHoleType(using ctx: Context) = new TypeMap() {
337+
override def apply(tp: Type): Type = tp match
338+
case tp: TypeRef if tp.typeSymbol.isSplice =>
339+
apply(tp.dealias)
340+
case tp @ TypeRef(pre, _) if pre == NoPrefix || pre.termSymbol.isLocal =>
341+
val hiBound = tp.typeSymbol.info match
342+
case info @ ClassInfo(_, _, classParents, _, _) => classParents.reduce(_ & _)
343+
case info => info.hiBound
344+
apply(hiBound)
345+
case tp =>
346+
mapOver(tp)
347+
}
348+
349+
/** Remove references to local types that will not be defined in this quote */
350+
def getTermHoleType(using ctx: Context) = new TypeMap() {
351+
override def apply(tp: Type): Type = tp match
352+
case tp @ TypeRef(NoPrefix, _) if capturers.contains(tp.symbol) =>
353+
// reference to term with a type defined in outer quote
354+
getTypeHoleType(tp)
355+
case tp @ TermRef(NoPrefix, _) if capturers.contains(tp.symbol) =>
356+
// widen term refs to terms defined in outer quote
357+
apply(tp.widenTermRefExpr)
358+
case tp =>
359+
mapOver(tp)
360+
}
361+
362+
val holeType = if isTermHole then getTermHoleType(tpe) else getTypeHoleType(tpe)
363+
364+
Hole(isTermHole, idx, splices).withType(holeType).asInstanceOf[Hole]
335365
}
336366

337367
override def transform(tree: Tree)(implicit ctx: Context): Tree =

tasty/src/dotty/tools/tasty/TastyFormat.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ Standard Section: "Comments" Comment*
249249
object TastyFormat {
250250

251251
final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F)
252-
val MajorVersion: Int = 21
252+
val MajorVersion: Int = 22
253253
val MinorVersion: Int = 0
254254

255255
/** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */

tests/pos-macros/i7110a/Macro_1.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.quoted._
2+
3+
object Macros {
4+
5+
inline def m[R](sym: Symantics[R]) : R = ${ mImpl[R]('{sym}) }
6+
7+
def mImpl[R: Type](using qctx: QuoteContext)(sym: Expr[Symantics[R]]): Expr[R] = '{
8+
$sym.Meth(42)
9+
}
10+
}
11+
12+
trait Symantics[R] {
13+
def Meth(exp: Int): R
14+
def Meth(): R
15+
}

tests/pos-macros/i7110a/Test_2.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import scala.quoted._
2+
import Macros._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
7+
val sym = new Symantics[Int] {
8+
def Meth(exp: Int): Int = exp
9+
def Meth(): Int = 42
10+
}
11+
12+
val test = m[Int](sym)
13+
}
14+
}

0 commit comments

Comments
 (0)