From 7064bff2299a2d2c1da97b1a13e53442f034a3c1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 16 Mar 2023 11:56:03 +0100 Subject: [PATCH 01/10] 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 e959f5d1451d..82d30efb086c 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 a2c5523638cd4bfc8cf90854bccff367b05156f7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Mar 2023 09:18:27 +0100 Subject: [PATCH 02/10] 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 62174c806f09..263b9743b798 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 82d30efb086c..6f9b04e28198 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, 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 f9240d6091c4..03fc2c87c9f0 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -658,11 +658,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 @@ -670,7 +670,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 f480f2b0cb8483bdebc0d9422fff25acb79c5829 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Mar 2023 10:03:21 +0100 Subject: [PATCH 03/10] Split Hole from PrickledHole --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 36 ++++++++++++++++--- 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 + 7 files changed, 47 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/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 16b256e69059..a21b25c4a4f5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3035,6 +3035,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 99e979b475305143e5f19778848e86d9d151079b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 23 Mar 2023 16:09:52 +0100 Subject: [PATCH 04/10] Improve hole tree checker --- .../tools/dotc/transform/TreeChecker.scala | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 03fc2c87c9f0..3a67dbc47fcb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -662,24 +662,29 @@ object TreeChecker { // 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 <- (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) + for targ <- targs do + assert(targ.isType) + assert(targ.tpe.isInstanceOf[TypeRef], "Expected TypeRef in Hole targs but got: " + targ.tpe) + for arg <- args do + assert(arg.isTerm) + assert(arg.isInstanceOf[RefTree], "Expected RefTree in Hole args but got: " + arg) // Check result type of the hole if isTermHole then assert(tpt.typeOpt <:< pt) else assert(tpt.typeOpt =:= pt) // Check that the types of the args conform to the types of the contents of the hole - val argQuotedTypes = (targs ::: args).map { arg => - if arg.isTerm then - val tpe = arg.typeOpt.widenTermRefExpr match - case _: MethodicType => - // Special erasure for captured function references - // See `SpliceTransformer.transformCapturedApplication` - defn.AnyType - case tpe => tpe - defn.QuotedExprClass.typeRef.appliedTo(tpe) - else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) + val targQuotedTypes = targs.map { arg => + defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) + } + val argQuotedTypes = args.map { arg => + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpe) } val expectedResultType = if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) @@ -687,7 +692,7 @@ object TreeChecker { val contextualResult = defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) val expectedContentType = - defn.FunctionOf(argQuotedTypes, contextualResult) + defn.FunctionOf(targQuotedTypes ::: argQuotedTypes, contextualResult) assert(content.typeOpt =:= expectedContentType, i"unexpected content of hole\nexpected: ${expectedContentType}\nwas: ${content.typeOpt}") tree1 From 3fecbe36a2cf84fafc7123617fe480e5dd2954c2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 24 Mar 2023 09:32:23 +0100 Subject: [PATCH 05/10] Rename `PickledHole` to `TastyQuoteHole` --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 24 +++++++++---------- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++-- .../tools/dotc/quoted/PickledQuotes.scala | 6 ++--- .../tools/dotc/typer/QuotesAndSplices.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index ef57e1121cbf..e06597cb6e0b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -977,7 +977,7 @@ object Trees { /** 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. + * as TastyQuoteHole 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. @@ -1006,8 +1006,8 @@ object Trees { * @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] + 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 } @@ -1134,7 +1134,7 @@ object Trees { type Thicket = Trees.Thicket[T] type Hole = Trees.Hole[T] - type PickledHole = Trees.PickledHole[T] + type TastyQuoteHole = Trees.TastyQuoteHole[T] @sharable val EmptyTree: Thicket = genericEmptyTree @sharable val EmptyValDef: ValDef = genericEmptyValDef @@ -1364,9 +1364,9 @@ object Trees { 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))) + 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 @@ -1391,8 +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) + 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) } @@ -1525,8 +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 tree @ TastyQuoteHole(_, _, args, tpt) => + cpy.TastyQuoteHole(tree)(args = transform(args), tpt = transform(tpt)) case _ => transformMoreCases(tree) } @@ -1668,7 +1668,7 @@ object Trees { this(x, ts) case Hole(_, _, targs, args, content, tpt) => this(this(this(this(x, targs), args), content), tpt) - case PickledHole(_, _, args, tpt) => + case TastyQuoteHole(_, _, args, tpt) => this(this(x, args), tpt) case _ => foldMoreCases(x, tree) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 34c9e6393cfc..8eb628faf327 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -427,7 +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) + 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 ----------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index d84fb9dc33a1..95b94c9342c3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1439,7 +1439,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - PickledHole(true, idx, args, TypeTree(tpe)).withType(tpe) + TastyQuoteHole(true, idx, args, TypeTree(tpe)).withType(tpe) case _ => readPathTerm() } @@ -1473,7 +1473,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - PickledHole(false, idx, args, TypeTree(tpe)).withType(tpe) + TastyQuoteHole(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 a3a3d5d8bf16..244f6792bfcc 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -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 PickledHole(isTermHole, idx, args, _) => + case TastyQuoteHole(isTermHole, idx, args, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { if isTermHole then val quotedExpr = termHole match @@ -165,7 +165,7 @@ object PickledQuotes { val tree = typeHole match case TypeHole.V1(evalHole) => tdef.rhs match - case TypeBoundsTree(_, PickledHole(_, 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) @@ -173,7 +173,7 @@ 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 TastyQuoteHole(_, 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 90c81cf018d3..b6dae3eb37b9 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -168,7 +168,7 @@ trait QuotesAndSplices { val tpt = typedType(tree.tpt) assignType(tree, tpt) - def typedPickledHole(tree: untpd.PickledHole, pt: Type)(using Context): Tree = + def typedTastyQuoteHole(tree: untpd.TastyQuoteHole, pt: Type)(using Context): Tree = val tpt = typedType(tree.tpt) assignType(tree, tpt) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 625153f6f93f..745c7315ff90 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -536,7 +536,7 @@ 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 = + def assignType(tree: untpd.TastyQuoteHole, tpt: Tree)(using Context): TastyQuoteHole = tree.withType(tpt.tpe) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a21b25c4a4f5..846418100198 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3035,7 +3035,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 tree: untpd.TastyQuoteHole => typedTastyQuoteHole(tree, pt) case _ => typedUnadapted(desugar(tree, pt), pt, locked) } From 904909b184de09482718b4c0e255d6d107d32252 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 6 Apr 2023 11:04:38 +0200 Subject: [PATCH 06/10] Update Hole syntax in RefinedPrinter and documentation --- .../tools/dotc/printing/RefinedPrinter.scala | 4 +-- .../tools/dotc/transform/PickleQuotes.scala | 35 +++++++++---------- .../dotty/tools/dotc/transform/Splicing.scala | 25 ++++++------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 2107cabbb501..2255f19a72df 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -725,12 +725,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case MacroTree(call) => keywordStr("macro ") ~ toTextGlobal(call) case Hole(isTermHole, idx, targs, args, content, tpt) => - val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]") + 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) val contentText = ("|" ~~ toTextGlobal(content)).provided(content ne EmptyTree) - prefix ~~ idx.toString ~ targsText ~ argsText ~ ":" ~~ tptText ~~ contentText ~~ postfix + 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/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 263b9743b798..930f7672dcd9 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -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 * }, * ) * ``` diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 6f9b04e28198..3e1ca2cef72f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -38,18 +38,19 @@ object Splicing: * * After this phase we have the invariant where all splices have the following shape * ``` - * {{{ | | * | (*) => }}} + * ${ ``[*](*): | (*, *) => } * ``` - * where `` does not contain any free references to quoted definitions and `*` + * where `` does not contain any free references to quoted definitions and `*`/`*` * contains the quotes with references to all cross-quote references. There are some special rules * for references in the LHS of assignments and cross-quote method references. * - * In the following code example `x1` and `x2` are cross-quote references. + * In the following code example `x1`, `x2` and `U` are cross-quote references. * ``` * '{ ... * val x1: T1 = ??? - * val x2: T2 = ??? - * ${ (q: Quotes) ?=> f('{ g(x1, x2) }) }: T3 + * type U <: T2 + * val x2: U = ??? + * ${ (q: Quotes) ?=> f('{ g[U](x1, x2) }) }: T3 * } * ``` * @@ -59,12 +60,12 @@ object Splicing: * ``` * '{ ... * val x1: T1 = ??? - * val x2: T2 = ??? - * {{{ 0 | T3 | x1, x2 | - * (x1$: Expr[T1], x2$: Expr[T2]) => // body of this lambda does not contain references to x1 or x2 - * (q: Quotes) ?=> f('{ g(${x1$}, ${x2$}) }) - * - * }}} + * type U <: T2 + * val x2: U = ??? + * ${ `0`[U](x1, x2): T3 | + * (U$2: Type[U], x1$: Expr[T], x2$: Expr[U]) => // body of this lambda does not contain references to x1 or x2 + * (q: Quotes) ?=> f('{ @SplicedType type U$3 = $[ `0`: U | U$1 ]; g[U$3](${x1$}, ${x2$}) }) + * } * } * ``` * @@ -184,7 +185,7 @@ class Splicing extends MacroTransform: * ``` * is transformed into * ```scala - * {{{ | T2 | x, X | (x$1: Expr[T1], X$1: Type[X]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} }}} + * ${ ``[X](x): T2 | (X$1: Type[X], x$1: Expr[T1]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} } * ``` */ private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: From d6f36458540eb6fa4012becdf9a4a5a5c706fc8d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 12 Apr 2023 09:18:40 +0200 Subject: [PATCH 07/10] Use deterministic order of hole arguments --- compiler/src/dotty/tools/dotc/transform/Splicing.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 3e1ca2cef72f..d9258004901b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -189,8 +189,8 @@ class Splicing extends MacroTransform: * ``` */ private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: - private val typeBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] - private val termBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] + private val typeBindingMap = mutable.LinkedHashMap.empty[Symbol, (Tree, Symbol)] + private val termBindingMap = mutable.LinkedHashMap.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 From fd2b69794d6bc5ec46aeabfb17dab1f102d50050 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 12 Apr 2023 09:52:36 +0200 Subject: [PATCH 08/10] Fix typo in documentation --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index e06597cb6e0b..e5ffa2d898df 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -995,7 +995,7 @@ 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 + * 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 From 50545a735a68d85afac25cd4ca5a287ee0d7c975 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 12 Apr 2023 10:00:16 +0200 Subject: [PATCH 09/10] Improve documentation --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index e5ffa2d898df..279dea7d7d32 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -976,8 +976,11 @@ 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 encoding pickled quotes. These will be encoded - * as TastyQuoteHole when pickled. + * 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. @@ -993,7 +996,9 @@ 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). + * 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 From 04277310aebe85c8d77922c6669b4f3acfca125b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 12 Apr 2023 10:00:37 +0200 Subject: [PATCH 10/10] Add regression test --- tests/pos-macros/captured-type/Macro_1.scala | 8 ++++++++ tests/pos-macros/captured-type/Test_2.scala | 3 +++ 2 files changed, 11 insertions(+) create mode 100644 tests/pos-macros/captured-type/Macro_1.scala create mode 100644 tests/pos-macros/captured-type/Test_2.scala diff --git a/tests/pos-macros/captured-type/Macro_1.scala b/tests/pos-macros/captured-type/Macro_1.scala new file mode 100644 index 000000000000..72bb3d35704b --- /dev/null +++ b/tests/pos-macros/captured-type/Macro_1.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +inline def foo[U](u: U): U = ${ fooImpl[U]('u) } + +def fooImpl[U: Type](u: Expr[U])(using Quotes): Expr[U] = '{ + def f[T](x: T): T = ${ identity('{ x: T }) } + f[U]($u) +} \ No newline at end of file diff --git a/tests/pos-macros/captured-type/Test_2.scala b/tests/pos-macros/captured-type/Test_2.scala new file mode 100644 index 000000000000..ed1baab565e0 --- /dev/null +++ b/tests/pos-macros/captured-type/Test_2.scala @@ -0,0 +1,3 @@ +def test = + foo(1) + foo("abc")