Skip to content

Make sure captured types are listed before terms #17144

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
wants to merge 10 commits into from
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
64 changes: 51 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,47 @@ 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 TastyQuoteHole when pickled. These holes will be inserted in the
* Staging phase and pickled (without the content) as TASTy HOLE in the
* PickleQuotes phase.
*
* @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 unpicking quotes TASTy HOLE. These holes will
* only be present in pickled quotes. These are unpickled and replaced
* with other trees in PickledQuotes.
*
* Hole created by this compiler 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.
Comment on lines +1005 to +1007
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to instead have the Unpickler separate the type args from the term args when unpickling an older tasty file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be ideal. Unfortunately, we cannot do that because we must pass all these arguments in the order they were listed. This is a consequence of the old format's interleaved type and term arguments. Specifically, the functions of ExprHole.V1 and ExprHole.V2 take these arguments in the order they are listed in the hole.

*
* @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 TastyQuoteHole[+T <: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] {
type ThisTree[+T <: Untyped] <: TastyQuoteHole[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 +1139,7 @@ object Trees {
type Thicket = Trees.Thicket[T]

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

@sharable val EmptyTree: Thicket = genericEmptyTree
@sharable val EmptyValDef: ValDef = genericEmptyValDef
Expand Down Expand Up @@ -1337,9 +1365,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 TastyQuoteHole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], tpt: Tree)(using Context): TastyQuoteHole = tree match {
case tree: TastyQuoteHole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && tpt.eq(tree.tpt) => tree
case _ => finalize(tree, untpd.TastyQuoteHole(isTerm, idx, args, tpt)(sourceFile(tree)))
}

// Copier methods with default arguments; these demand that the original tree
Expand All @@ -1362,8 +1394,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 TastyQuoteHole(tree: TastyQuoteHole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, tpt: Tree = tree.tpt)(using Context): TastyQuoteHole =
TastyQuoteHole(tree: Tree)(isTerm, idx, args, tpt)

}

Expand Down Expand Up @@ -1494,8 +1528,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 @ TastyQuoteHole(_, _, args, tpt) =>
cpy.TastyQuoteHole(tree)(args = transform(args), tpt = transform(tpt))
case _ =>
transformMoreCases(tree)
}
Expand Down Expand Up @@ -1635,8 +1671,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 TastyQuoteHole(_, _, 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 TastyQuoteHole(isTermHole: Boolean, idx: Int, args: List[Tree], tpt: Tree)(implicit src: SourceFile): TastyQuoteHole = new TastyQuoteHole(isTermHole, idx, args, tpt)

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

Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -665,11 +665,12 @@ class TreePickler(pickler: TastyPickler) {
pickleTree(hi)
pickleTree(alias)
}
case Hole(_, idx, args, _, tpt) =>
case Hole(_, idx, targs, args, _, tpt) =>
writeByte(HOLE)
withLength {
writeNat(idx)
pickleType(tpt.tpe, richTypes = true)
targs.foreach(pickleTree)
args.foreach(pickleTree)
}
}
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)
TastyQuoteHole(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)
TastyQuoteHole(false, idx, args, TypeTree(tpe)).withType(tpe)
case _ =>
if (isTypeTreeTag(nextByte)) readTerm()
else {
Expand Down
11 changes: 6 additions & 5 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) =>
val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]")
val argsText = toTextGlobal(args, ", ")
val contentText = toTextGlobal(content)
case Hole(isTermHole, idx, targs, args, content, tpt) =>
val (prefix, postfix) = if isTermHole then ("${", "}") else ("$[", "]")
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
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,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 TastyQuoteHole(isTermHole, idx, args, _) =>
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
if isTermHole then
val quotedExpr = termHole match
Expand Down Expand Up @@ -165,15 +165,15 @@ object PickledQuotes {
val tree = typeHole match
case TypeHole.V1(evalHole) =>
tdef.rhs match
case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) =>
case TypeBoundsTree(_, TastyQuoteHole(_, 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 TastyQuoteHole(_, idx, _, _) = tdef.rhs: @unchecked
PickledQuotes.quotedTypeToTree(types.nn.apply(idx))
(tdef.symbol, tree.tpe)
}.toMap
Expand Down
37 changes: 18 additions & 19 deletions compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,42 @@ import scala.annotation.constructorOnly
* Transforms top level quote
* ```
* '{ ...
* @TypeSplice type X0 = {{ 0 | .. | contentsTpe0 | .. }}
* @TypeSplice type X2 = {{ 1 | .. | contentsTpe1 | .. }}
* @TypeSplice type X0 = $[ `0`: ... | contentsTpe0 ]
* type X1
* val x1: U1 = ???
* val x2: U2 = ???
* ...
* {{{ 3 | x1 | contents0 | T0 }}} // hole
* ${ `2`(x1): T0 | contents0 } // Hole
* ...
* {{{ 4 | x2 | contents1 | T1 }}} // hole
* ${ `3`[X1](x2): T1 | contents1 } // Hole
* ...
* {{{ 5 | x1, x2 | contents2 | T2 }}} // hole
* ${ `4`(x1, x2): T2 | contents2 } // Hole
* ...
* }
* ```
* to
* ```
* unpickleExprV2(
* pickled = [[ // PICKLED TASTY
* @TypeSplice type X0 // with bounds that do not contain captured types
* @TypeSplice type X1 // with bounds that do not contain captured types
* pickled = tasty""" // PICKLED TASTY
* @TypeSplice type X0 // with bounds that do not contain captured types
* type X1
* val x1 = ???
* val x2 = ???
* ...
* {{{ 0 | x1 | | T0 }}} // hole
* ...
* {{{ 1 | x2 | | T1 }}} // hole
* ...
* {{{ 2 | x1, x2 | | T2 }}} // hole
* ${ `0`(x1): T0 } // Hole
* ...
* ${ `1`[X1](x2): T1 } // Hole
* ...
* ${ `2`(x1, x2): T2 } // Hole
* ...
* ]],
* """,
* typeHole = (idx: Int, args: List[Any]) => idx match {
* case 0 => contentsTpe0.apply(args(0).asInstanceOf[Type[?]]) // beta reduced
* case 1 => contentsTpe1.apply(args(0).asInstanceOf[Type[?]]) // beta reduced
* },
* termHole = (idx: Int, args: List[Any], quotes: Quotes) => idx match {
* case 3 => content0.apply(args(0).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced
* case 4 => content1.apply(args(0).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced
* case 5 => content2.apply(args(0).asInstanceOf[Expr[U1]], args(1).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced
* case 0 => content0.apply(args(0).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced
* case 1 => content1.apply(args(0).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced
* case 2 => content2.apply(args(0).asInstanceOf[Expr[U1]], args(1).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced
* },
* )
* ```
Expand Down Expand Up @@ -126,7 +125,7 @@ class PickleQuotes extends MacroTransform {
private val contents = List.newBuilder[Tree]
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
tree match
case tree @ Hole(isTerm, _, _, content, _) =>
case tree @ Hole(isTerm, _, _, _, content, _) =>
if !content.isEmpty then
contents += content
val holeType =
Expand Down
Loading