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..279dea7d7d32 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -976,20 +976,47 @@ object Trees { def genericEmptyTree[T <: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]] /** Tree that replaces a level 1 splices in pickled (level 0) quotes. - * It is only used when picking quotes (will never be in a TASTy file). + * + * It is only used when encoding pickled quotes. These will be encoded + * as TastyQuoteHole when pickled. These holes will be inserted in the + * Staging phase and pickled (without the content) as TASTy HOLE in the + * PickleQuotes phase. * * @param isTermHole If this hole is a term, otherwise it is a type hole. * @param idx The index of the hole in it's enclosing level 0 quote. - * @param args The arguments of the splice to compute its content - * @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle. + * @param targs The type arguments of the splice to compute its content + * @param args The term (or type) arguments of the splice to compute its content * @param tpt Type of the hole + * @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle. */ - case class Hole[+T <: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { + case class Hole[+T <: Untyped](isTermHole: Boolean, idx: Int, targs: List[Tree[T]], args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[+T <: Untyped] <: Hole[T] override def isTerm: Boolean = isTermHole override def isType: Boolean = !isTermHole } + /** Tree that replaces a level 1 splices in pickled (level 0) quotes. + * It is only used when unpicking quotes TASTy HOLE. These holes will + * only be present in pickled quotes. These are unpickled and replaced + * with other trees in PickledQuotes. + * + * Hole created by this compiler separate the targs from the args. Holes + * generated with 3.0-3.3 contain all type args and targs in any order in + * a single list. For backwards compatibility we read holes from tasty as + * if they had no targs and have only args. Therefore the args may contain + * type trees. + * + * @param isTermHole If this hole is a term, otherwise it is a type hole. + * @param idx The index of the hole in it's enclosing level 0 quote. + * @param args The term (or type) arguments of the splice to compute its content + * @param tpt Type of the hole + */ + case class TastyQuoteHole[+T <: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { + type ThisTree[+T <: Untyped] <: TastyQuoteHole[T] + override def isTerm: Boolean = isTermHole + override def isType: Boolean = !isTermHole + } + def flatten[T <: Untyped](trees: List[Tree[T]]): List[Tree[T]] = { def recur(buf: ListBuffer[Tree[T]] | Null, remaining: List[Tree[T]]): ListBuffer[Tree[T]] | Null = remaining match { @@ -1112,6 +1139,7 @@ object Trees { type Thicket = Trees.Thicket[T] type Hole = Trees.Hole[T] + type TastyQuoteHole = Trees.TastyQuoteHole[T] @sharable val EmptyTree: Thicket = genericEmptyTree @sharable val EmptyValDef: ValDef = genericEmptyValDef @@ -1337,9 +1365,13 @@ object Trees { case tree: Thicket if (trees eq tree.trees) => tree case _ => finalize(tree, untpd.Thicket(trees)(sourceFile(tree))) } - def Hole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match { - case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && content.eq(tree.content) && content.eq(tree.content) => tree - case _ => finalize(tree, untpd.Hole(isTerm, idx, args, content, tpt)(sourceFile(tree))) + def Hole(tree: Tree)(isTerm: Boolean, idx: Int, targs: List[Tree], args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match { + case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && targs.eq(tree.targs) && args.eq(tree.args) && content.eq(tree.content) && tpt.eq(tree.tpt) => tree + case _ => finalize(tree, untpd.Hole(isTerm, idx, targs, args, content, tpt)(sourceFile(tree))) + } + def TastyQuoteHole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], tpt: Tree)(using Context): TastyQuoteHole = tree match { + case tree: TastyQuoteHole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && tpt.eq(tree.tpt) => tree + case _ => finalize(tree, untpd.TastyQuoteHole(isTerm, idx, args, tpt)(sourceFile(tree))) } // Copier methods with default arguments; these demand that the original tree @@ -1362,8 +1394,10 @@ object Trees { TypeDef(tree: Tree)(name, rhs) def Template(tree: Template)(using Context)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody): Template = Template(tree: Tree)(constr, parents, derived, self, body) - def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole = - Hole(tree: Tree)(isTerm, idx, args, content, tpt) + def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, targs: List[Tree] = tree.targs, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole = + Hole(tree: Tree)(isTerm, idx, targs, args, content, tpt) + def TastyQuoteHole(tree: TastyQuoteHole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, tpt: Tree = tree.tpt)(using Context): TastyQuoteHole = + TastyQuoteHole(tree: Tree)(isTerm, idx, args, tpt) } @@ -1494,8 +1528,10 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) - case tree @ Hole(_, _, args, content, tpt) => - cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt)) + case tree @ Hole(_, _, targs, args, content, tpt) => + cpy.Hole(tree)(targs = transform(targs), args = transform(args), content = transform(content), tpt = transform(tpt)) + case tree @ TastyQuoteHole(_, _, args, tpt) => + cpy.TastyQuoteHole(tree)(args = transform(args), tpt = transform(tpt)) case _ => transformMoreCases(tree) } @@ -1635,8 +1671,10 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case Hole(_, _, args, content, tpt) => - this(this(this(x, args), content), tpt) + case Hole(_, _, targs, args, content, tpt) => + this(this(this(this(x, targs), args), content), tpt) + case TastyQuoteHole(_, _, args, tpt) => + this(this(x, args), tpt) case _ => foldMoreCases(x, tree) } 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..8eb628faf327 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -426,7 +426,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors) def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats) def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot) - def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, args, content, tpt) + def Hole(isTermHole: Boolean, idx: Int, targs: List[Tree], args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, targs, args, content, tpt) + def TastyQuoteHole(isTermHole: Boolean, idx: Int, args: List[Tree], tpt: Tree)(implicit src: SourceFile): TastyQuoteHole = new TastyQuoteHole(isTermHole, idx, args, tpt) // ------ Additional creation methods for untyped only ----------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 8a396921f32b..84dfc2569be6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -665,11 +665,14 @@ 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) + if targs.nonEmpty then + writeByte(HOLETYPES) + withLength { 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..b20ec63e71d9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1436,10 +1436,7 @@ class TreeUnpickler(reader: TastyReader, val alias = if currentAddr == end then EmptyTree else readTpt() createNullableTypeBoundsTree(lo, hi, alias) case HOLE => - val idx = readNat() - val tpe = readType() - val args = until(end)(readTerm()) - Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) + readHole(end, isTerm = true) case _ => readPathTerm() } @@ -1470,10 +1467,7 @@ class TreeUnpickler(reader: TastyReader, case HOLE => readByte() val end = readEnd() - val idx = readNat() - val tpe = readType() - val args = until(end)(readTerm()) - Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) + readHole(end, isTerm = false) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { @@ -1506,6 +1500,17 @@ class TreeUnpickler(reader: TastyReader, setSpan(start, CaseDef(pat, guard, rhs)) } + def readHole(end: Addr, isTerm: Boolean)(using Context): Tree = + val idx = readNat() + val tpe = readType() + val targs = + if nextByte == HOLETYPES then + readByte() + until(readEnd()) { readTpt() } + else Nil + val args = until(end) { readTerm() } + TastyQuoteHole(true, idx, targs ::: args, TypeTree(tpe)).withType(tpe) + def readLater[T <: AnyRef](end: Addr, op: TreeReader => Context ?=> T)(using Context): Trees.Lazy[T] = readLaterWithOwner(end, op)(ctx.owner) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 014e5ddf0d66..2255f19a72df 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) => - val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]") - val argsText = toTextGlobal(args, ", ") - val contentText = toTextGlobal(content) + case Hole(isTermHole, idx, targs, args, content, tpt) => + val (prefix, postfix) = if isTermHole then ("${", "}") else ("$[", "]") + val targsText = ("[" ~ toTextGlobal(targs, ", ") ~ "]").provided(targs.nonEmpty) + val argsText = ("(" ~ toTextGlobal(args, ", ") ~ ")").provided(args.nonEmpty) val tptText = toTextGlobal(tpt) - prefix ~~ idx.toString ~~ "|" ~~ tptText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix + val contentText = ("|" ~~ toTextGlobal(content)).provided(content ne EmptyTree) + prefix ~~ "`" ~ idx.toString ~ "`" ~ targsText ~ argsText ~ ":" ~~ tptText ~~ contentText ~~ postfix case CapturingTypeTree(refs, parent) => parent match case ImpureByNameTypeTree(bntpt) => diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 20bcba417a5e..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 Hole(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(_, Hole(_, idx, args, _, _), _) => + case TypeBoundsTree(_, TastyQuoteHole(_, idx, args, _), _) => // To keep for backwards compatibility. In some older version holes where created in the bounds. val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) PickledQuotes.quotedTypeToTree(quotedType) @@ -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 Hole(_, 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/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 62174c806f09..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 * }, * ) * ``` @@ -126,7 +125,7 @@ class PickleQuotes extends MacroTransform { private val contents = List.newBuilder[Tree] override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match - case tree @ Hole(isTerm, _, _, content, _) => + case tree @ Hole(isTerm, _, _, _, content, _) => if !content.isEmpty then contents += content val holeType = diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index bb82fba32a7c..a4ea3c860a50 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$}) }) + * } * } * ``` * @@ -132,7 +133,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) @@ -184,11 +185,12 @@ 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: - private var refBindingMap = 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 @@ -196,14 +198,16 @@ 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 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) 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 @@ -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 diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index f9240d6091c4..3a67dbc47fcb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -658,28 +658,33 @@ 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 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 || arg.tpe.isInstanceOf[TypeRef], "Expected TypeRef in Hole type args but got: " + arg.tpe) + 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 targQuotedTypes = targs.map { arg => + defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt.widenTermRefExpr) + } val argQuotedTypes = 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 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 diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 8473bd168bc5..b6dae3eb37b9 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 typedTastyQuoteHole(tree: untpd.TastyQuoteHole, 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..745c7315ff90 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.TastyQuoteHole, tpt: Tree)(using Context): TastyQuoteHole = + 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..846418100198 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.TastyQuoteHole => typedTastyQuoteHole(tree, pt) case _ => typedUnadapted(desugar(tree, pt), pt, locked) } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index cb15d82affb8..fb100fc3ba60 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -37,7 +37,8 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyBuffer.reset"), ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyFormat.APPLYsigpoly"), ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyHash.pjwHash64"), - ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.util.Util.dble") + ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.util.Util.dble"), + ProblemFilters.exclude[DirectMissingMethodProblem]("dotty.tools.tasty.TastyFormat.HOLETYPES"), ) val Interfaces: Seq[ProblemFilter] = Seq( ProblemFilters.exclude[ReversedMissingMethodProblem]("dotty.tools.dotc.interfaces.Diagnostic.diagnosticRelatedInformation"), diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 226fc14acb39..48cb937d54a2 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -122,7 +122,10 @@ 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 HoleTypes? arg_Tree* -- Splice hole with index `idx`, the type of the hole `tpe`, type arguments of the hole `targ`s and term arguments of the hole `arg`s + -- Note: From 3.0 to 3.3 `args` could contain type arguments as well. This is no longer the case. + HoleTypes = HOLETYPES Length targ_Tree* + CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? -- case pat if guard => rhs @@ -586,6 +589,7 @@ object TastyFormat { final val MATCHtpt = 191 final val MATCHCASEtype = 192 + final val HOLETYPES = 254 final val HOLE = 255 final val firstNatTreeTag = SHAREDterm @@ -600,6 +604,7 @@ object TastyFormat { firstASTTreeTag <= tag && tag <= BOUNDED || firstNatASTTreeTag <= tag && tag <= NAMEDARG || firstLengthTreeTag <= tag && tag <= MATCHtpt || + tag == HOLETYPES || tag == HOLE def isParamTag(tag: Int): Boolean = tag == PARAM || tag == TYPEPARAM @@ -804,6 +809,7 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" case HOLE => "HOLE" + case HOLETYPES => "HOLETYPES" } /** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry. 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")