From 388d92e5f41b7bc4a07e1b43beae78134a630b64 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 16 Mar 2023 11:56:03 +0100 Subject: [PATCH 1/6] Make sure captured types are listed before terms --- .../src/dotty/tools/dotc/transform/Splicing.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index bb82fba32a7c..7f89634ee09d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -188,7 +188,8 @@ class Splicing extends MacroTransform: * ``` */ private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: - private var refBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] + private val typeBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] + private val termBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] /** Reference to the `Quotes` instance of the current level 1 splice */ private var quotes: Tree | Null = null // TODO: add to the context private var healedTypes: QuoteTypeTags | Null = null // TODO: add to the context @@ -196,7 +197,10 @@ class Splicing extends MacroTransform: def transformSplice(tree: tpd.Tree, tpe: Type, holeIdx: Int)(using Context): tpd.Tree = assert(level == 0) val newTree = transform(tree) - val (refs, bindings) = refBindingMap.values.toList.unzip + val (typeRefs, typeBindings) = typeBindingMap.values.toList.unzip + val (termRefs, termBindings) = termBindingMap.values.toList.unzip + val refs = typeRefs ::: termRefs + val bindings = typeBindings ::: termBindings val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) val methType = MethodType(bindingsTypes, newTree.tpe) val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType) @@ -348,7 +352,7 @@ class Splicing extends MacroTransform: Param, defn.QuotedExprClass.typeRef.appliedTo(tpe), ) - val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2 + val bindingSym = termBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2 ref(bindingSym) private def newQuotedTypeClassBinding(tpe: Type)(using Context) = @@ -361,7 +365,7 @@ class Splicing extends MacroTransform: private def capturedType(tree: Tree)(using Context): Symbol = val tpe = tree.tpe.widenTermRefExpr - val bindingSym = refBindingMap + val bindingSym = typeBindingMap .getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newQuotedTypeClassBinding(tpe)))._2 bindingSym @@ -371,7 +375,7 @@ class Splicing extends MacroTransform: val capturePartTypes = new TypeMap { def apply(tp: Type) = tp match { case typeRef: TypeRef if containsCapturedType(typeRef) => - val termRef = refBindingMap + val termRef = typeBindingMap .getOrElseUpdate(typeRef.symbol, (TypeTree(typeRef), newQuotedTypeClassBinding(typeRef)))._2.termRef val tagRef = healedTypes.nn.getTagRef(termRef) tagRef From 67b16d0ed36e9bace82ccf5dee48152f0860f992 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Mar 2023 09:18:27 +0100 Subject: [PATCH 2/6] Add type argument list to the `Hole` tree --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 5 +-- compiler/src/dotty/tools/dotc/ast/Trees.scala | 31 ++++++++++++------- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +-- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 3 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 6 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 9 +++--- .../tools/dotc/quoted/PickledQuotes.scala | 12 +++---- .../tools/dotc/transform/PickleQuotes.scala | 2 +- .../dotty/tools/dotc/transform/Splicing.scala | 5 ++- .../tools/dotc/transform/TreeChecker.scala | 6 ++-- 11 files changed, 48 insertions(+), 37 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index faeafae97f5e..968974620e5e 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -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) } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index c0b5987c3875..8a600671828b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -978,13 +978,20 @@ object Trees { /** 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 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 @@ -1337,9 +1344,9 @@ 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) && content.eq(tree.content) => tree + case _ => finalize(tree, untpd.Hole(isTerm, idx, targs, args, content, tpt)(sourceFile(tree))) } // Copier methods with default arguments; these demand that the original tree @@ -1362,8 +1369,8 @@ 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) } @@ -1494,8 +1501,8 @@ 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 _ => transformMoreCases(tree) } @@ -1635,8 +1642,8 @@ 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 _ => foldMoreCases(x, tree) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index d1b1cdf607b5..e9aac976d6c9 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -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 ------------------------------------------------------ diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index a262c3658399..28c162d4d52e 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -426,7 +426,7 @@ 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) // ------ Additional creation methods for untyped only ----------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 8a396921f32b..b73e37c3b8c5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -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) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 9078a8959112..8ed88740253e 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1438,8 +1438,9 @@ class TreeUnpickler(reader: TastyReader, case HOLE => val idx = readNat() val tpe = readType() + val targs = Nil // TODO read tags seprately from args val args = until(end)(readTerm()) - Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) + Hole(true, idx, targs, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => readPathTerm() } @@ -1472,8 +1473,9 @@ class TreeUnpickler(reader: TastyReader, val end = readEnd() val idx = readNat() val tpe = readType() + val targs = Nil // TODO read tags seprately from args val args = until(end)(readTerm()) - Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) + Hole(false, idx, targs, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 014e5ddf0d66..2107cabbb501 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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) => diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 20bcba417a5e..091344b2fbef 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -100,14 +100,14 @@ 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 Hole(isTermHole, idx, targs, args, _, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { if isTermHole then val quotedExpr = termHole match case ExprHole.V1(evalHole) => - evalHole.nn.apply(idx, reifyExprHoleV1Args(args), QuotesImpl()) + evalHole.nn.apply(idx, reifyExprHoleV1Args(targs ::: args), QuotesImpl()) case ExprHole.V2(evalHole) => - evalHole.nn.apply(idx, reifyExprHoleV2Args(args), QuotesImpl()) + evalHole.nn.apply(idx, reifyExprHoleV2Args(targs ::: args), QuotesImpl()) val filled = PickledQuotes.quotedExprToTree(quotedExpr) @@ -165,15 +165,15 @@ object PickledQuotes { val tree = typeHole match case TypeHole.V1(evalHole) => tdef.rhs match - case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) => + case TypeBoundsTree(_, Hole(_, idx, targs, args, _, _), _) => // To keep for backwards compatibility. In some older version holes where created in the bounds. - val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(targs ::: 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 Hole(_, idx, _, _, _, _) = tdef.rhs: @unchecked PickledQuotes.quotedTypeToTree(types.nn.apply(idx)) (tdef.symbol, tree.tpe) }.toMap diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 728ee9552c81..0b496c074e7c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -126,7 +126,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 = diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 7f89634ee09d..879b8e8f4f4b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -132,7 +132,7 @@ class Splicing extends MacroTransform: case None => val holeIdx = numHoles numHoles += 1 - val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) + val hole = tpd.Hole(false, holeIdx, Nil, Nil, ref(qual), TypeTree(tp)) typeHoles.put(qual.symbol, hole) hole cpy.TypeDef(tree)(rhs = hole) @@ -199,7 +199,6 @@ class Splicing extends MacroTransform: val newTree = transform(tree) val (typeRefs, typeBindings) = typeBindingMap.values.toList.unzip val (termRefs, termBindings) = termBindingMap.values.toList.unzip - val refs = typeRefs ::: termRefs val bindings = typeBindings ::: termBindings val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) val methType = MethodType(bindingsTypes, newTree.tpe) @@ -207,7 +206,7 @@ class Splicing extends MacroTransform: val ddef = DefDef(meth, List(bindings), newTree.tpe, newTree.changeOwner(ctx.owner, meth)) val fnType = defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType))) - tpd.Hole(true, holeIdx, refs, closure, TypeTree(tpe)) + tpd.Hole(true, holeIdx, typeRefs, termRefs, closure, TypeTree(tpe)) override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 6d904d1f3cc6..b94ef8dc77e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -653,11 +653,11 @@ object TreeChecker { super.typedPackageDef(tree) override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = { - val tree1 @ Hole(isTermHole, _, args, content, tpt) = super.typedHole(tree, pt): @unchecked + val tree1 @ Hole(isTermHole, _, targs, args, content, tpt) = super.typedHole(tree, pt): @unchecked // Check that we only add the captured type `T` instead of a more complex type like `List[T]`. // If we have `F[T]` with captured `F` and `T`, we should list `F` and `T` separately in the args. - for arg <- args do + for arg <- (targs ::: args) do // TODO check targs and terms separately assert(arg.isTerm || arg.tpe.isInstanceOf[TypeRef], "Expected TypeRef in Hole type args but got: " + arg.tpe) // Check result type of the hole @@ -665,7 +665,7 @@ object TreeChecker { else assert(tpt.typeOpt =:= pt) // Check that the types of the args conform to the types of the contents of the hole - val argQuotedTypes = args.map { arg => + val argQuotedTypes = (targs ::: args).map { arg => if arg.isTerm then val tpe = arg.typeOpt.widenTermRefExpr match case _: MethodicType => From 5c691104ae444d5b79d2d64d4912b166151704a3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Mar 2023 10:03:21 +0100 Subject: [PATCH 3/6] Split Hole from PrickledHole --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 36 ++++++++++++++++--- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 ++ compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 6 ++-- .../tools/dotc/quoted/PickledQuotes.scala | 12 +++---- .../tools/dotc/typer/QuotesAndSplices.scala | 4 +++ .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 1 + 8 files changed, 50 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 8a600671828b..ef57e1121cbf 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -975,6 +975,23 @@ object Trees { def genericEmptyValDef[T <: Untyped]: ValDef[T] = theEmptyValDef.asInstanceOf[ValDef[T]] 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 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 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, 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). * @@ -986,13 +1003,11 @@ object 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 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, 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] + 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 } @@ -1119,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 @@ -1345,9 +1361,13 @@ object Trees { case _ => finalize(tree, untpd.Thicket(trees)(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) && content.eq(tree.content) => tree + 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 // is of the same class as the copy. We only include trees with more than 2 elements here. @@ -1371,6 +1391,8 @@ object Trees { Template(tree: Tree)(constr, parents, derived, self, body) 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) } @@ -1503,6 +1525,8 @@ object Trees { if (trees1 eq trees) tree else Thicket(trees1) 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) } @@ -1644,6 +1668,8 @@ object Trees { this(x, ts) 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) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index e9aac976d6c9..d6fd20fb496f 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -394,6 +394,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { 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) + // def PickledHole(isTermHole: Boolean, idx: Int, args: List[Tree], tpt: Tree)(using Context): PickledHole = + // ta.assignType(untpd.PickledHole(isTermHole, idx, args, tpt), tpt) + // ------ Making references ------------------------------------------------------ def prefixIsElidable(tp: NamedType)(using Context): Boolean = { diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 28c162d4d52e..34c9e6393cfc 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -427,6 +427,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { 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, 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 ----------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 8ed88740253e..d84fb9dc33a1 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1438,9 +1438,8 @@ class TreeUnpickler(reader: TastyReader, case HOLE => val idx = readNat() val tpe = readType() - val targs = Nil // TODO read tags seprately from args val args = until(end)(readTerm()) - Hole(true, idx, targs, args, EmptyTree, TypeTree(tpe)).withType(tpe) + PickledHole(true, idx, args, TypeTree(tpe)).withType(tpe) case _ => readPathTerm() } @@ -1473,9 +1472,8 @@ class TreeUnpickler(reader: TastyReader, val end = readEnd() val idx = readNat() val tpe = readType() - val targs = Nil // TODO read tags seprately from args val args = until(end)(readTerm()) - Hole(false, idx, targs, args, EmptyTree, TypeTree(tpe)).withType(tpe) + PickledHole(false, idx, args, TypeTree(tpe)).withType(tpe) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 091344b2fbef..a3a3d5d8bf16 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -100,14 +100,14 @@ 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, targs, args, _, _) => + case PickledHole(isTermHole, idx, args, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { if isTermHole then val quotedExpr = termHole match case ExprHole.V1(evalHole) => - evalHole.nn.apply(idx, reifyExprHoleV1Args(targs ::: args), QuotesImpl()) + evalHole.nn.apply(idx, reifyExprHoleV1Args(args), QuotesImpl()) case ExprHole.V2(evalHole) => - evalHole.nn.apply(idx, reifyExprHoleV2Args(targs ::: args), QuotesImpl()) + evalHole.nn.apply(idx, reifyExprHoleV2Args(args), QuotesImpl()) val filled = PickledQuotes.quotedExprToTree(quotedExpr) @@ -165,15 +165,15 @@ object PickledQuotes { val tree = typeHole match case TypeHole.V1(evalHole) => tdef.rhs match - case TypeBoundsTree(_, Hole(_, idx, targs, 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(targs ::: args)) + 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 PickledHole(_, idx, _, _) = tdef.rhs: @unchecked PickledQuotes.quotedTypeToTree(types.nn.apply(idx)) (tdef.symbol, tree.tpe) }.toMap diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 8473bd168bc5..90c81cf018d3 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -168,6 +168,10 @@ trait QuotesAndSplices { val tpt = typedType(tree.tpt) assignType(tree, tpt) + def typedPickledHole(tree: untpd.PickledHole, pt: Type)(using Context): Tree = + val tpt = typedType(tree.tpt) + assignType(tree, tpt) + private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit = if (level == 0 && !ctx.owner.ownersIterator.exists(_.isInlineMethod)) report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 98e9cb638c17..625153f6f93f 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -536,6 +536,8 @@ trait TypeAssigner { def assignType(tree: untpd.Hole, tpt: Tree)(using Context): Hole = tree.withType(tpt.tpe) + def assignType(tree: untpd.PickledHole, tpt: Tree)(using Context): PickledHole = + tree.withType(tpt.tpe) } object TypeAssigner extends TypeAssigner: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8e8c8b2da1ca..08d733af4b35 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3014,6 +3014,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.Splice => typedSplice(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here case tree: untpd.Hole => typedHole(tree, pt) + case tree: untpd.PickledHole => typedPickledHole(tree, pt) case _ => typedUnadapted(desugar(tree, pt), pt, locked) } From 01ee569cb57866525afbd9924740eb0230ef3a04 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 16 Mar 2023 11:10:48 +0100 Subject: [PATCH 4/6] Define splice hole in library Fixes #17137 --- .../dotty/tools/dotc/core/Definitions.scala | 3 + .../tools/dotc/core/tasty/TreePickler.scala | 11 +--- .../tools/dotc/quoted/PickledQuotes.scala | 35 ++++++++++- .../tools/dotc/transform/PickleQuotes.scala | 58 ++++++++++++++----- .../scala/quoted/runtime/QuoteUnpickler.scala | 8 +++ project/MiMaFilters.scala | 2 + tasty/src/dotty/tools/tasty/TastyFormat.scala | 2 +- 7 files changed, 92 insertions(+), 27 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 148b314220a8..07c267bc4100 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index b73e37c3b8c5..7d59a34dfc1c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -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) @@ -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 @@ -665,14 +664,6 @@ class TreePickler(pickler: TastyPickler) { pickleTree(hi) pickleTree(alias) } - case Hole(_, idx, targs, args, _, tpt) => - writeByte(HOLE) - withLength { - writeNat(idx) - pickleType(tpt.tpe, richTypes = true) - targs.foreach(pickleTree) - args.foreach(pickleTree) - } } catch { case ex: TypeError => diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index a3a3d5d8bf16..af6725c728cc 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -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._ @@ -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 { @@ -173,7 +202,11 @@ object PickledQuotes { // To keep for backwards compatibility. In some older version we missed the creation of some holes. tpt case TypeHole.V2(types) => - val PickledHole(_, 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 diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 0b496c074e7c..59c897417653 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -33,11 +33,13 @@ import scala.annotation.constructorOnly * val x1: U1 = ??? * val x2: U2 = ??? * ... - * {{{ 3 | x1 | contents0 | T0 }}} // hole + * {{{ 3 | T0 | x1 | contents0 }}} // hole * ... - * {{{ 4 | x2 | contents1 | T1 }}} // hole + * {{{ 4 | T1 | x2 | contents1 }}} // hole * ... - * {{{ 5 | x1, x2 | contents2 | T2 }}} // hole + * {{{ 5 | T2 | x1, x2 | contents2 | }}} // hole + * ... + * {{{ 6 | T2 | X0, x1 | contents2 | }}} // hole * ... * } * ``` @@ -45,16 +47,18 @@ import scala.annotation.constructorOnly * ``` * 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 + * @TypeSplice type X0 = hole[0, ..] // with bounds that do not contain captured types + * @TypeSplice type X1 = hole[1, ..]// with bounds that do not contain captured types * val x1 = ??? * val x2 = ??? * ... - * {{{ 0 | x1 | | T0 }}} // hole - * ... - * {{{ 1 | x2 | | T1 }}} // hole - * ... - * {{{ 2 | x1, x2 | | T2 }}} // hole + * hole[3, T0, KNil](x1) // hole + * ... + * hole[4, T1, KNil](x2) // hole + * ... + * hole[5, T2, KNil](x1, x2) // hole + * ... + * hole[6, T2, KCons[X0, KNil]](x1) // hole * ... * ]], * typeHole = (idx: Int, args: List[Any]) => idx match { @@ -65,6 +69,7 @@ import scala.annotation.constructorOnly * 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 6 => content2.apply(args(0).asInstanceOf[Type[?]], args(1).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced * }, * ) * ``` @@ -126,13 +131,36 @@ 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, idx, targs, args, content, _) => if !content.isEmpty then contents += content - val holeType = - if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe) - val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType)) - if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole + if isTerm then // transform into method call `hole[idx, T', Targs](args*)` + val args1 = args.filter(_.isTerm).map { arg => + def fullyAppliedToDummyArgs(arg: Tree, tpe: Type): Tree = + tpe match + case tpe: MethodType => + fullyAppliedToDummyArgs(arg.appliedToArgs(tpe.paramNames.map(_ => tpd.ref(defn.Predef_undefined))), tpe.resultType) + case tpe: PolyType => + fullyAppliedToDummyArgs(arg.appliedToTypes(tpe.paramInfos.map(_.loBound)), tpe.resultType) + case _ => arg + fullyAppliedToDummyArgs(arg, arg.tpe.widenTermRefExpr) + } + val holeType = getTermHoleType(tree.tpe) + val typeArgs = tpd.hkNestedPairsTypeTree(targs).tpe + val holeTypeArgs = List(ConstantType(Constant(idx)), holeType, typeArgs) + val newHole = + ref(defn.QuoteUnpickler_exprHole) + .appliedToTypes(holeTypeArgs) + .appliedToVarargs(args1, TypeTree(defn.AnyType)) + .withSpan(tree.span) + Inlined(EmptyTree, Nil, newHole).withSpan(tree.span) + else // transform into type `hole[idx, T']` + assert(targs.isEmpty) + assert(args.isEmpty) + val holeType = getTypeHoleType(tree.tpe) + val holeTypeArgs = List(ConstantType(Constant(idx)), holeType) + TypeTree(AppliedType(defn.QuoteUnpickler_typeHole.typeRef, holeTypeArgs)) + .withSpan(tree.span) case tree: DefTree => val newAnnotations = tree.symbol.annotations.mapconserve { annot => annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol))) diff --git a/library/src/scala/quoted/runtime/QuoteUnpickler.scala b/library/src/scala/quoted/runtime/QuoteUnpickler.scala index 63e62658cbb4..ba5e5905edc2 100644 --- a/library/src/scala/quoted/runtime/QuoteUnpickler.scala +++ b/library/src/scala/quoted/runtime/QuoteUnpickler.scala @@ -1,5 +1,6 @@ package scala.quoted.runtime +import scala.annotation.compileTimeOnly import scala.quoted.{Quotes, Expr, Type} /** Part of the Quotes interface that needs to be implemented by the compiler but is not visible to users */ @@ -32,3 +33,10 @@ trait QuoteUnpickler: * Generated for code compiled with Scala 3.2.0+ */ def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Null | Seq[Type[?]]): scala.quoted.Type[T] + +object QuoteUnpickler: + @compileTimeOnly("Illegal reference to `scala.quoted.runtime.QuoteUnpickler.hole`") + def hole[Idx <: Int, T, TArgs](args: Any*): T = ??? + + @compileTimeOnly("Illegal reference to `scala.quoted.runtime.QuoteUnpickler.hole`") + type hole[Idx <: Int, T] <: T diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index cb15d82affb8..9386a0f50ba0 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -17,6 +17,8 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Break"), ProblemFilters.exclude[MissingClassProblem]("scala.util.boundary$Label"), ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteMatching$"), + ProblemFilters.exclude[MissingClassProblem]("scala.quoted.runtime.QuoteUnpickler$"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.hole"), // Scala.js only: new runtime support class in 3.2.3; not available to users ProblemFilters.exclude[MissingClassProblem]("scala.scalajs.runtime.AnonFunctionXXL"), diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 226fc14acb39..b5188cae1ebb 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -122,7 +122,7 @@ Standard-Section: "ASTs" TopLevelStat* MATCHtpt Length bound_Term? sel_Term CaseDef* -- sel match { CaseDef } where `bound` is optional upper bound of all rhs BYNAMEtpt underlying_Term -- => underlying SHAREDterm term_ASTRef -- Link to previously serialized term - HOLE Length idx_Nat tpe_Type arg_Tree* -- Splice hole with index `idx`, the type of the hole `tpe`, type and term arguments of the hole `arg`s + HOLE Length idx_Nat tpe_Type arg_Tree* -- Splice hole with index `idx`, the type of the hole `tpe`, type and term arguments of the hole `arg`s (only used in 3.0-3.3) CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? -- case pat if guard => rhs From 0be10af0a63b5b80a16bd0ef49cb8364c077ca09 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 23 Mar 2023 15:01:18 +0100 Subject: [PATCH 5/6] cleanup --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index d6fd20fb496f..e9aac976d6c9 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -394,9 +394,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { 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) - // def PickledHole(isTermHole: Boolean, idx: Int, args: List[Tree], tpt: Tree)(using Context): PickledHole = - // ta.assignType(untpd.PickledHole(isTermHole, idx, args, tpt), tpt) - // ------ Making references ------------------------------------------------------ def prefixIsElidable(tp: NamedType)(using Context): Boolean = { From bcaba2ef56c4256147d95ef6ea3beadd7571e98b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 23 Mar 2023 15:35:33 +0100 Subject: [PATCH 6/6] Use different indices for type and term holes --- .../tools/dotc/transform/PickleQuotes.scala | 28 +++++++++---------- .../dotc/transform/Splicing.scala | 14 ++++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 59c897417653..1d69007c92af 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -28,18 +28,18 @@ import scala.annotation.constructorOnly * Transforms top level quote * ``` * '{ ... - * @TypeSplice type X0 = {{ 0 | .. | contentsTpe0 | .. }} - * @TypeSplice type X2 = {{ 1 | .. | contentsTpe1 | .. }} + * @TypeSplice type X0 = [[[ 0 | .. | contentsTpe0 | .. ]]] + * @TypeSplice type X2 = [[[ 1 | .. | contentsTpe1 | .. ]]] * val x1: U1 = ??? * val x2: U2 = ??? * ... - * {{{ 3 | T0 | x1 | contents0 }}} // hole + * {{{ 0 | T0 | x1 | contents0 }}} // hole * ... - * {{{ 4 | T1 | x2 | contents1 }}} // hole + * {{{ 1 | T1 | x2 | contents1 }}} // hole * ... - * {{{ 5 | T2 | x1, x2 | contents2 | }}} // hole + * {{{ 2 | T2 | x1, x2 | contents2 | }}} // hole * ... - * {{{ 6 | T2 | X0, x1 | contents2 | }}} // hole + * {{{ 3 | T2 | X0, x1 | contents2 | }}} // hole * ... * } * ``` @@ -52,13 +52,13 @@ import scala.annotation.constructorOnly * val x1 = ??? * val x2 = ??? * ... - * hole[3, T0, KNil](x1) // hole + * hole[0, T0, KNil](x1) // hole * ... - * hole[4, T1, KNil](x2) // hole + * hole[1, T1, KNil](x2) // hole * ... - * hole[5, T2, KNil](x1, x2) // hole + * hole[2, T2, KNil](x1, x2) // hole * ... - * hole[6, T2, KCons[X0, KNil]](x1) // hole + * hole[3, T2, KCons[X0, KNil]](x1) // hole * ... * ]], * typeHole = (idx: Int, args: List[Any]) => idx match { @@ -66,10 +66,10 @@ import scala.annotation.constructorOnly * 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 6 => content2.apply(args(0).asInstanceOf[Type[?]], args(1).asInstanceOf[Expr[U1]]).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 + * case 3 => content2.apply(args(0).asInstanceOf[Type[?]], args(1).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced * }, * ) * ``` diff --git a/tests/pos-with-compiler-cc/dotc/transform/Splicing.scala b/tests/pos-with-compiler-cc/dotc/transform/Splicing.scala index 3a3aa7e89445..3a096b7e52a1 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Splicing.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Splicing.scala @@ -105,8 +105,10 @@ class Splicing extends MacroTransform: /** Set of definitions in the current quote */ private val quotedDefs = mutable.Set.empty[Symbol] - /** Number of holes created in this quote. Used for indexing holes. */ - private var numHoles = 0 + /** Number of term holes created in this quote. Used for indexing term holes. */ + private var numTermHoles = 0 + /** Number of type holes created in this quote. Used for indexing type holes. */ + private var numTypeHoles = 0 /** Mapping from the term symbol of a `Type[T]` to it's hole. Used to deduplicate type holes. */ private val typeHoles = mutable.Map.empty[Symbol, Hole] @@ -118,8 +120,8 @@ class Splicing extends MacroTransform: val splicedCode1 = super.transform(splicedCode)(using spliceContext) cpy.Apply(tree)(fn, List(splicedCode1)) else - val holeIdx = numHoles - numHoles += 1 + val holeIdx = numTermHoles + numTermHoles += 1 val splicer = SpliceTransformer(ctx.owner, quotedDefs.contains) val newSplicedCode1 = splicer.transformSplice(splicedCode, tree.tpe, holeIdx)(using spliceContext) val newSplicedCode2 = Level0QuoteTransformer.transform(newSplicedCode1)(using spliceContext) @@ -130,8 +132,8 @@ class Splicing extends MacroTransform: val hole = typeHoles.get(qual.symbol) match case Some (hole) => cpy.Hole(hole)(content = EmptyTree) case None => - val holeIdx = numHoles - numHoles += 1 + val holeIdx = numTypeHoles + numTypeHoles += 1 val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) typeHoles.put(qual.symbol, hole) hole