-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #7110: Fix splice overload #8561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
fd7ff85
a296199
bfa631a
daabc81
cebfdd8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,44 +61,117 @@ object PickledQuotes { | |
}.apply(tp) | ||
|
||
/** Unpickle the tree contained in the TastyExpr */ | ||
def unpickleExpr(tasty: PickledQuote, args: PickledArgs)(implicit ctx: Context): Tree = { | ||
def unpickleExpr(tasty: PickledQuote, splices: PickledArgs)(implicit ctx: Context): Tree = { | ||
val tastyBytes = TastyString.unpickle(tasty) | ||
val unpickled = unpickle(tastyBytes, args, isType = false)(ctx.addMode(Mode.ReadPositions)) | ||
/** Force unpickling of the tree, removes the spliced type `@quotedTypeTag type` definitions and dealiases references to `@quotedTypeTag type` */ | ||
val forceAndCleanArtefacts = new TreeMap { | ||
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { | ||
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => | ||
assert(rest.forall { case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) }) | ||
transform(expr1) | ||
case tree => super.transform(tree).withType(dealiasTypeTags(tree.tpe)) | ||
} | ||
} | ||
forceAndCleanArtefacts.transform(unpickled) | ||
val unpickled = unpickle(tastyBytes, splices, isType = false)(ctx.addMode(Mode.ReadPositions)) | ||
val Inlined(call, Nil, expnasion) = unpickled | ||
val inlineCtx = inlineContext(call) | ||
val expansion1 = spliceTypes(expnasion, splices)(using inlineCtx) | ||
val expansion2 = spliceTerms(expansion1, splices)(using inlineCtx) | ||
cpy.Inlined(unpickled)(call, Nil, expansion2) | ||
} | ||
|
||
/** Unpickle the tree contained in the TastyType */ | ||
def unpickleType(tasty: PickledQuote, args: PickledArgs)(implicit ctx: Context): Tree = { | ||
val tastyBytes = TastyString.unpickle(tasty) | ||
val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions)) | ||
val tpt = unpickled match { | ||
case Block(aliases, tpt) => | ||
// `@quoteTypeTag type` aliases are not required after unpickling. | ||
// Type definitions are placeholders for type holes in the pickled quote, at this point | ||
// those holes have been filled. As we already dealias al references to them in `dealiasTypeTags` | ||
// there is no need to keep their definitions in the tree. As artifacts of quote reification | ||
// they also do not have a meaningful position in the source. | ||
val aliases1 = aliases.filter(!_.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) | ||
seq(aliases1, tpt) | ||
case tpt => tpt | ||
spliceTypes(unpickled, args) | ||
} | ||
|
||
/** Replace all term holes with the spliced terms */ | ||
private def spliceTerms(tree: Tree, splices: PickledArgs)(using Context): Tree = { | ||
val evaluateHoles = new TreeMap { | ||
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { | ||
case Hole(isTerm, idx, args) => | ||
val reifiedArgs = args.map { arg => | ||
if (arg.isTerm) (qctx: scala.quoted.QuoteContext) ?=> new TastyTreeExpr(arg, QuoteContext.scopeId) | ||
else new TreeType(arg, QuoteContext.scopeId) | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if isTerm then | ||
val splice1 = splices(idx).asInstanceOf[Seq[Any] => scala.quoted.QuoteContext ?=> quoted.Expr[?]] | ||
val quotedExpr = splice1(reifiedArgs)(using dotty.tools.dotc.quoted.QuoteContext()) | ||
val filled = PickledQuotes.quotedExprToTree(quotedExpr) | ||
|
||
// We need to make sure a hole is created with the source file of the surrounding context, even if | ||
// it filled with contents a different source file. | ||
if filled.source == ctx.source then filled | ||
else filled.cloneIn(ctx.source).withSpan(tree.span) | ||
else | ||
// Replaces type holes generated by ReifyQuotes (non-spliced types). | ||
// These are types defined in a quote and used at the same level in a nested quote. | ||
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](reifiedArgs) | ||
PickledQuotes.quotedTypeToTree(quotedType) | ||
case tree: Select => | ||
// Retain selected members | ||
val qual = transform(tree.qualifier) | ||
qual.select(tree.symbol).withSpan(tree.span) | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
case tree => | ||
if tree.isDef then | ||
tree.symbol.annotations = tree.symbol.annotations.map { | ||
annot => annot.derivedAnnotation(transform(annot.tree)) | ||
} | ||
end if | ||
|
||
val tree1 = super.transform(tree) | ||
tree1.withType(mapAnnots(tree1.tpe)) | ||
} | ||
|
||
// Evaluate holes in type annotations | ||
private val mapAnnots = new TypeMap { | ||
override def apply(tp: Type): Type = { | ||
tp match | ||
case tp @ AnnotatedType(underlying, annot) => | ||
val underlying1 = this(underlying) | ||
derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) | ||
case _ => mapOver(tp) | ||
} | ||
} | ||
} | ||
tpt.withType(dealiasTypeTags(tpt.tpe)) | ||
nicolasstucki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
val tree1 = evaluateHoles.transform(tree) | ||
quotePickling.println(i"**** evaluated quote\n$tree1") | ||
tree1 | ||
} | ||
|
||
/** Replace all type holes generated with the spliced types */ | ||
private def spliceTypes(tree: Tree, splices: PickledArgs)(using Context): Tree = { | ||
tree match | ||
case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => | ||
val typeSpliceMap = (stat :: rest).iterator.map { | ||
case tdef: TypeDef => | ||
assert(tdef.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) | ||
val tree = tdef.rhs match | ||
case TypeBoundsTree(_, Hole(_, idx, args), _) => | ||
val quotedType = splices(idx).asInstanceOf[Seq[Any] => quoted.Type[?]](args) | ||
PickledQuotes.quotedTypeToTree(quotedType) | ||
case TypeBoundsTree(_, tpt, _) => | ||
tpt | ||
(tdef.symbol, tree.tpe) | ||
}.toMap | ||
class ReplaceSplicedTyped extends TypeMap() { | ||
override def apply(tp: Type): Type = { | ||
val tp1 = tp match { | ||
case tp: TypeRef => | ||
typeSpliceMap.get(tp.symbol) match | ||
case Some(t) if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) => t | ||
case None => tp | ||
case _ => tp | ||
} | ||
mapOver(tp1) | ||
} | ||
} | ||
val expansion2 = new TreeTypeMap(new ReplaceSplicedTyped).transform(expr1) | ||
quotePickling.println(i"**** typed quote\n${expansion2.show}") | ||
expansion2 | ||
case _ => | ||
tree | ||
} | ||
|
||
// TASTY picklingtests/pos/quoteTest.scala | ||
|
||
/** Pickle tree into it's TASTY bytes s*/ | ||
private def pickle(tree: Tree)(implicit ctx: Context): Array[Byte] = { | ||
quotePickling.println(i"**** pickling quote of \n${tree.show}") | ||
quotePickling.println(i"**** pickling quote of\n$tree") | ||
val pickler = new TastyPickler(defn.RootClass) | ||
val treePkl = pickler.treePkl | ||
treePkl.pickle(tree :: Nil) | ||
|
@@ -122,7 +195,13 @@ object PickledQuotes { | |
unpickler.enter(Set.empty) | ||
|
||
val tree = unpickler.tree | ||
quotePickling.println(i"**** unpickle quote ${tree.show}") | ||
|
||
// Make sure trees and positions are fully loaded | ||
new TreeTraverser { | ||
def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree) | ||
}.traverse(tree) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What will happen if we remove the code above? Maybe add a doc for consequences if the action is not taken. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some symbols and positions are not loaded early enough. Not sure why though. But it has been like that since the start. Dimitry mentioned the need for this for loading trees from tasty in general and we have keep this mechanism so far. |
||
|
||
quotePickling.println(i"**** unpickled quote\n$tree") | ||
tree | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -331,7 +331,37 @@ class ReifyQuotes extends MacroTransform { | |
*/ | ||
private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = { | ||
val idx = embedded.addTree(body, NoSymbol) | ||
Hole(isTermHole, idx, splices).withType(tpe).asInstanceOf[Hole] | ||
|
||
/** Remove references to local types that will not be defined in this quote */ | ||
def getTypeHoleType(using ctx: Context) = new TypeMap() { | ||
override def apply(tp: Type): Type = tp match | ||
case tp: TypeRef if tp.typeSymbol.isSplice => | ||
apply(tp.dealias) | ||
case tp @ TypeRef(pre, _) if pre == NoPrefix || pre.termSymbol.isLocal => | ||
val hiBound = tp.typeSymbol.info match | ||
case info @ ClassInfo(_, _, classParents, _, _) => classParents.reduce(_ & _) | ||
case info => info.hiBound | ||
apply(hiBound) | ||
case tp => | ||
mapOver(tp) | ||
} | ||
|
||
/** Remove references to local types that will not be defined in this quote */ | ||
def getTermHoleType(using ctx: Context) = new TypeMap() { | ||
override def apply(tp: Type): Type = tp match | ||
case tp @ TypeRef(NoPrefix, _) if capturers.contains(tp.symbol) => | ||
// reference to term with a type defined in outer quote | ||
getTypeHoleType(tp) | ||
case tp @ TermRef(NoPrefix, _) if capturers.contains(tp.symbol) => | ||
// widen term refs to terms defined in outer quote | ||
apply(tp.widenTermRefExpr) | ||
case tp => | ||
mapOver(tp) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will approximation of the hole type have consequences on member resolution in unpickling? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the consequence is that when unpickling we use the same known bounds of the types to resolve members selection as was done in typed. This is the key to make sure they do not change. |
||
|
||
val holeType = if isTermHole then getTermHoleType(tpe) else getTypeHoleType(tpe) | ||
|
||
Hole(isTermHole, idx, splices).withType(holeType).asInstanceOf[Hole] | ||
} | ||
|
||
override def transform(tree: Tree)(implicit ctx: Context): Tree = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import scala.quoted._ | ||
|
||
object Macros { | ||
|
||
inline def m[R](sym: Symantics[R]) : R = ${ mImpl[R]('{sym}) } | ||
|
||
def mImpl[R: Type](using qctx: QuoteContext)(sym: Expr[Symantics[R]]): Expr[R] = '{ | ||
$sym.Meth(42) | ||
} | ||
} | ||
|
||
trait Symantics[R] { | ||
def Meth(exp: Int): R | ||
def Meth(): R | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import scala.quoted._ | ||
import Macros._ | ||
|
||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
|
||
val sym = new Symantics[Int] { | ||
def Meth(exp: Int): Int = exp | ||
def Meth(): Int = 42 | ||
} | ||
|
||
val test = m[Int](sym) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import scala.quoted._ | ||
|
||
object Macros { | ||
|
||
inline def m[T](sym: Symantics {type R = T}) : T = ${ mImpl[T]('{sym}) } | ||
|
||
def mImpl[T: Type](using qctx: QuoteContext)(sym: Expr[Symantics { type R = T }]): Expr[T] = '{ | ||
$sym.Meth(42) | ||
} | ||
} | ||
|
||
trait Symantics { | ||
type R | ||
def Meth(exp: Int): R | ||
def Meth(): R | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import scala.quoted._ | ||
import Macros._ | ||
|
||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
|
||
val sym = new Symantics { | ||
type R = Int | ||
def Meth(exp: Int): Int = exp | ||
def Meth(): Int = 42 | ||
} | ||
|
||
val test = m(sym) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import scala.quoted._ | ||
|
||
object Macros { | ||
|
||
inline def m[R](sym: Symantics[R]) : R = ${ mImpl[R]('{sym}) } | ||
|
||
def mImpl[R: Type](using qctx: QuoteContext)(sym: Expr[Symantics[R]]): Expr[R] = '{ | ||
$sym.Meth(42) | ||
} | ||
} | ||
|
||
trait Symantics[R] { | ||
def Meth(exp: Int): R | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import scala.quoted._ | ||
import Macros._ | ||
|
||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
|
||
val sym = new Symantics2 | ||
|
||
val test = m[Int](sym) | ||
} | ||
} | ||
|
||
class Symantics2 extends Symantics[Int] { | ||
def Meth(exp: Int): Int = exp | ||
def Meth(): Int = 42 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import scala.quoted._ | ||
|
||
object Macros { | ||
|
||
inline def m(sym: Symantics) : Int = ${ mImpl('sym) } | ||
|
||
def mImpl(using qctx: QuoteContext)(sym: Expr[Symantics]): Expr[Int] = '{ | ||
$sym.Meth(42) | ||
} | ||
} | ||
|
||
trait Symantics { | ||
def Meth(exp: Int): Int | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small opt: can we just do one pass for splicing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that was the first approach I tried. We need the types to be known when we expand the term holes as some of them may take some type parameters that the user inspects. Note that
spliceTypes
is a no-op if the quote does not have type splices.