Skip to content

Define splice hole in library #17140

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ class TreeTypeMap(
val bind1 = tmap.transformSub(bind)
val expr1 = tmap.transform(expr)
cpy.Labeled(labeled)(bind1, expr1)
case tree @ Hole(_, _, args, content, tpt) =>
case tree @ Hole(_, _, targs, args, content, tpt) =>
val targs1 = targs.mapConserve(transform)
val args1 = args.mapConserve(transform)
val content1 = transform(content)
val tpt1 = transform(tpt)
cpy.Hole(tree)(args = args1, content = content1, tpt = tpt1)
cpy.Hole(tree)(targs = targs1, args = args1, content = content1, tpt = tpt1)
case tree1 =>
super.transform(tree1)
}
Expand Down
59 changes: 46 additions & 13 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -976,20 +976,42 @@ object Trees {
def genericEmptyTree[T <: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]]

/** Tree that replaces a level 1 splices in pickled (level 0) quotes.
* It is only used when picking quotes (will never be in a TASTy file).
* It is only used when encoding pickled quotes. These will be encoded
* as PickledHole when pickled.
*
* @param isTermHole If this hole is a term, otherwise it is a type hole.
* @param idx The index of the hole in it's enclosing level 0 quote.
* @param args The arguments of the splice to compute its content
* @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle.
* @param targs The type arguments of the splice to compute its content
* @param args The term (or type) arguments of the splice to compute its content
* @param tpt Type of the hole
* @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle.
*/
case class Hole[+T <: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] {
case class Hole[+T <: Untyped](isTermHole: Boolean, idx: Int, targs: List[Tree[T]], args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] {
type ThisTree[+T <: Untyped] <: Hole[T]
override def isTerm: Boolean = isTermHole
override def isType: Boolean = !isTermHole
}

/** Tree that replaces a level 1 splices in pickled (level 0) quotes.
* It is only used when picking quotes (will never be in a TASTy file).
*
* Hole created by this compile separate the targs from the args. Holes
* generated with 3.0-3.3 contain all type args and targs in any order in
* a single list. For backwards compatibility we read holes from tasty as
* if they had no targs and have only args. Therefore the args may contain
* type trees.
*
* @param isTermHole If this hole is a term, otherwise it is a type hole.
* @param idx The index of the hole in it's enclosing level 0 quote.
* @param args The term (or type) arguments of the splice to compute its content
* @param tpt Type of the hole
*/
case class PickledHole[+T <: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] {
type ThisTree[+T <: Untyped] <: PickledHole[T]
override def isTerm: Boolean = isTermHole
override def isType: Boolean = !isTermHole
}

def flatten[T <: Untyped](trees: List[Tree[T]]): List[Tree[T]] = {
def recur(buf: ListBuffer[Tree[T]] | Null, remaining: List[Tree[T]]): ListBuffer[Tree[T]] | Null =
remaining match {
Expand Down Expand Up @@ -1112,6 +1134,7 @@ object Trees {
type Thicket = Trees.Thicket[T]

type Hole = Trees.Hole[T]
type PickledHole = Trees.PickledHole[T]

@sharable val EmptyTree: Thicket = genericEmptyTree
@sharable val EmptyValDef: ValDef = genericEmptyValDef
Expand Down Expand Up @@ -1337,9 +1360,13 @@ object Trees {
case tree: Thicket if (trees eq tree.trees) => tree
case _ => finalize(tree, untpd.Thicket(trees)(sourceFile(tree)))
}
def Hole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match {
case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && content.eq(tree.content) && content.eq(tree.content) => tree
case _ => finalize(tree, untpd.Hole(isTerm, idx, args, content, tpt)(sourceFile(tree)))
def Hole(tree: Tree)(isTerm: Boolean, idx: Int, targs: List[Tree], args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match {
case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && targs.eq(tree.targs) && args.eq(tree.args) && content.eq(tree.content) && tpt.eq(tree.tpt) => tree
case _ => finalize(tree, untpd.Hole(isTerm, idx, targs, args, content, tpt)(sourceFile(tree)))
}
def PickledHole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], tpt: Tree)(using Context): PickledHole = tree match {
case tree: PickledHole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && tpt.eq(tree.tpt) => tree
case _ => finalize(tree, untpd.PickledHole(isTerm, idx, args, tpt)(sourceFile(tree)))
}

// Copier methods with default arguments; these demand that the original tree
Expand All @@ -1362,8 +1389,10 @@ object Trees {
TypeDef(tree: Tree)(name, rhs)
def Template(tree: Template)(using Context)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody): Template =
Template(tree: Tree)(constr, parents, derived, self, body)
def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole =
Hole(tree: Tree)(isTerm, idx, args, content, tpt)
def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, targs: List[Tree] = tree.targs, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole =
Hole(tree: Tree)(isTerm, idx, targs, args, content, tpt)
def PickledHole(tree: PickledHole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, tpt: Tree = tree.tpt)(using Context): PickledHole =
PickledHole(tree: Tree)(isTerm, idx, args, tpt)

}

Expand Down Expand Up @@ -1494,8 +1523,10 @@ object Trees {
case Thicket(trees) =>
val trees1 = transform(trees)
if (trees1 eq trees) tree else Thicket(trees1)
case tree @ Hole(_, _, args, content, tpt) =>
cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt))
case tree @ Hole(_, _, targs, args, content, tpt) =>
cpy.Hole(tree)(targs = transform(targs), args = transform(args), content = transform(content), tpt = transform(tpt))
case tree @ PickledHole(_, _, args, tpt) =>
cpy.PickledHole(tree)(args = transform(args), tpt = transform(tpt))
case _ =>
transformMoreCases(tree)
}
Expand Down Expand Up @@ -1635,8 +1666,10 @@ object Trees {
this(this(x, arg), annot)
case Thicket(ts) =>
this(x, ts)
case Hole(_, _, args, content, tpt) =>
this(this(this(x, args), content), tpt)
case Hole(_, _, targs, args, content, tpt) =>
this(this(this(this(x, targs), args), content), tpt)
case PickledHole(_, _, args, tpt) =>
this(this(x, args), tpt)
case _ =>
foldMoreCases(x, tree)
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def Throw(expr: Tree)(using Context): Tree =
ref(defn.throwMethod).appliedTo(expr)

def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole =
ta.assignType(untpd.Hole(isTermHole, idx, args, content, tpt), tpt)
def Hole(isTermHole: Boolean, idx: Int, targs: List[Tree], args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole =
ta.assignType(untpd.Hole(isTermHole, idx, targs, args, content, tpt), tpt)

// ------ Making references ------------------------------------------------------

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors)
def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats)
def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot)
def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, args, content, tpt)
def Hole(isTermHole: Boolean, idx: Int, targs: List[Tree], args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, targs, args, content, tpt)
def PickledHole(isTermHole: Boolean, idx: Int, args: List[Tree], tpt: Tree)(implicit src: SourceFile): PickledHole = new PickledHole(isTermHole, idx, args, tpt)

// ------ Additional creation methods for untyped only -----------------

Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,9 @@ class Definitions {
@tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler")
@tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2")
@tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2")
@tu lazy val QuoteUnpicklerModule: Symbol = requiredModule("scala.quoted.runtime.QuoteUnpickler")
@tu lazy val QuoteUnpickler_exprHole : Symbol = QuoteUnpicklerModule.requiredMethod("hole")
@tu lazy val QuoteUnpickler_typeHole : Symbol = QuoteUnpicklerModule.requiredType("hole")

@tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching")
@tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch")
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ class TreePickler(pickler: TastyPickler) {
pickleName(sym.name)
pickleParams
tpt match {
case _: Template | _: Hole => pickleTree(tpt)
case _: Template => pickleTree(tpt)
case _ if tpt.isType => pickleTpt(tpt)
}
pickleTreeUnlessEmpty(rhs)
Expand Down Expand Up @@ -413,7 +413,6 @@ class TreePickler(pickler: TastyPickler) {
var ename = tree.symbol.targetName
val selectFromQualifier =
name.isTypeName
|| qual.isInstanceOf[Hole] // holes have no symbol
|| sig == Signature.NotAMethod // no overload resolution necessary
|| !tree.denot.symbol.exists // polymorphic function type
|| tree.denot.asSingleDenotation.isRefinedMethod // refined methods have no defining class symbol
Expand Down Expand Up @@ -665,13 +664,6 @@ class TreePickler(pickler: TastyPickler) {
pickleTree(hi)
pickleTree(alias)
}
case Hole(_, idx, args, _, tpt) =>
writeByte(HOLE)
withLength {
writeNat(idx)
pickleType(tpt.tpe, richTypes = true)
args.foreach(pickleTree)
}
}
catch {
case ex: TypeError =>
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ class TreeUnpickler(reader: TastyReader,
val idx = readNat()
val tpe = readType()
val args = until(end)(readTerm())
Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe)
PickledHole(true, idx, args, TypeTree(tpe)).withType(tpe)
case _ =>
readPathTerm()
}
Expand Down Expand Up @@ -1473,7 +1473,7 @@ class TreeUnpickler(reader: TastyReader,
val idx = readNat()
val tpe = readType()
val args = until(end)(readTerm())
Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe)
PickledHole(false, idx, args, TypeTree(tpe)).withType(tpe)
case _ =>
if (isTypeTreeTag(nextByte)) readTerm()
else {
Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -724,12 +724,13 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
case MacroTree(call) =>
keywordStr("macro ") ~ toTextGlobal(call)
case Hole(isTermHole, idx, args, content, tpt) =>
case Hole(isTermHole, idx, targs, args, content, tpt) =>
val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]")
val argsText = toTextGlobal(args, ", ")
val contentText = toTextGlobal(content)
val targsText = ("[" ~ toTextGlobal(targs, ", ") ~ "]").provided(targs.nonEmpty)
val argsText = ("(" ~ toTextGlobal(args, ", ") ~ ")").provided(args.nonEmpty)
val tptText = toTextGlobal(tpt)
prefix ~~ idx.toString ~~ "|" ~~ tptText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix
val contentText = ("|" ~~ toTextGlobal(content)).provided(content ne EmptyTree)
prefix ~~ idx.toString ~ targsText ~ argsText ~ ":" ~~ tptText ~~ contentText ~~ postfix
case CapturingTypeTree(refs, parent) =>
parent match
case ImpureByNameTypeTree(bntpt) =>
Expand Down
39 changes: 36 additions & 3 deletions compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dotty.tools.dotc.quoted
import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.{TreeTypeMap, tpd}
import dotty.tools.dotc.config.Printers._
import dotty.tools.dotc.core.Constants._
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.Flags._
Expand Down Expand Up @@ -100,7 +101,7 @@ object PickledQuotes {
private def spliceTerms(tree: Tree, typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = {
def evaluateHoles = new TreeMap {
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match {
case Hole(isTermHole, idx, args, _, _) =>
case PickledHole(isTermHole, idx, args, _) =>
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
if isTermHole then
val quotedExpr = termHole match
Expand All @@ -125,6 +126,34 @@ object PickledQuotes {
val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args))
PickledQuotes.quotedTypeToTree(quotedType)
}
case Apply(TypeApply(hole, List(idxTree, tpt, targs)), List(Typed(SeqLiteral(args, _), _))) if hole.symbol == defn.QuoteUnpickler_exprHole =>
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
val idx = (idxTree.tpe: @unchecked) match
case ConstantType(Constant(idx: Int)) => idx

def kListTypes(tp: Type): List[TypeTree] = tp match
case AppliedType(kCons: TypeRef, headType :: tailType :: Nil) if kCons.symbol == defn.QuoteMatching_KCons =>
TypeTree(headType) :: kListTypes(tailType)
case kNil: TypeRef if kNil.symbol == defn.QuoteMatching_KNil =>
Nil

val targsList = kListTypes(targs.tpe)
val reifiedTypeArgs = reifyTypeHoleArgs(targsList)
val reifiedArgs = reifyTypeHoleArgs(targsList)

val argRefs = args.map(methPart) // strip dummy arguments

val quotedExpr = (termHole: @unchecked) match
case ExprHole.V2(evalHole) =>
evalHole.nn.apply(idx, reifiedTypeArgs ::: reifyExprHoleV2Args(argRefs), QuotesImpl())

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)
}
case tree =>
if tree.isDef then
tree.symbol.annotations = tree.symbol.annotations.map {
Expand Down Expand Up @@ -165,15 +194,19 @@ object PickledQuotes {
val tree = typeHole match
case TypeHole.V1(evalHole) =>
tdef.rhs match
case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) =>
case TypeBoundsTree(_, PickledHole(_, idx, args, _), _) =>
// To keep for backwards compatibility. In some older version holes where created in the bounds.
val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args))
PickledQuotes.quotedTypeToTree(quotedType)
case TypeBoundsTree(_, tpt, _) =>
// To keep for backwards compatibility. In some older version we missed the creation of some holes.
tpt
case TypeHole.V2(types) =>
val Hole(_, idx, _, _, _) = tdef.rhs: @unchecked
val idx = tdef.rhs match
case PickledHole(_, idx, _, _) => idx
case rhs: TypeTree =>
rhs.tpe match
case AppliedType(tycon, ConstantType(Constant(idx: Int)) :: _) if tycon.typeSymbol == defn.QuoteUnpickler_typeHole => idx
PickledQuotes.quotedTypeToTree(types.nn.apply(idx))
(tdef.symbol, tree.tpe)
}.toMap
Expand Down
Loading