From 0ee80f94a36bc523493fd9a8665d860e036537fd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 5 May 2023 13:06:47 +0200 Subject: [PATCH] Place staged type captures in Quote AST --- .../dotty/tools/dotc/CompilationUnit.scala | 2 +- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 21 ++- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- .../src/dotty/tools/dotc/core/Phases.scala | 4 + .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- .../dotty/tools/dotc/inlines/Inliner.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../tools/dotc/printing/RefinedPrinter.scala | 5 +- .../tools/dotc/staging/CrossStageSafety.scala | 80 +++++----- .../tools/dotc/staging/DirectTypeOf.scala | 25 ---- .../dotty/tools/dotc/staging/HealType.scala | 12 +- .../tools/dotc/staging/QuoteTypeTags.scala | 50 ++----- .../tools/dotc/transform/MegaPhase.scala | 2 +- .../tools/dotc/transform/PickleQuotes.scala | 137 ++++++++++++------ .../tools/dotc/transform/ReifiedReflect.scala | 2 +- .../dotty/tools/dotc/transform/Splicer.scala | 10 +- .../dotty/tools/dotc/transform/Splicing.scala | 89 ++++-------- .../dotty/tools/dotc/transform/Staging.scala | 2 +- .../tools/dotc/transform/TreeChecker.scala | 30 ++++ .../tools/dotc/transform/patmat/Space.scala | 2 +- .../tools/dotc/typer/QuotesAndSplices.scala | 6 +- .../src/dotty/tools/dotc/typer/ReTyper.scala | 3 +- 25 files changed, 249 insertions(+), 251 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/staging/DirectTypeOf.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 8e4989f13c3d..8415646eb16c 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -157,7 +157,7 @@ object CompilationUnit { if tree.symbol.is(Flags.Inline) then containsInline = true tree match - case tpd.Quote(_) => + case _: tpd.Quote => containsQuote = true case tree: tpd.Apply if tree.symbol == defn.QuotedTypeModule_of => containsQuote = true diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index e658a3e9a142..8a080972fb31 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1976,7 +1976,7 @@ object desugar { trees foreach collect case Block(Nil, expr) => collect(expr) - case Quote(body) => + case Quote(body, _) => new UntypedTreeTraverser { def traverse(tree: untpd.Tree)(using Context): Unit = tree match { case Splice(expr) => collect(expr) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 61009f48a8f0..f4c3240c03bd 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -690,9 +690,14 @@ object Trees { * when type checking. TASTy files will not contain type quotes. Type quotes are used again * in the `staging` phase to represent the reification of `Type.of[T]]`. * + * Type tags `tags` are always empty before the `staging` phase. Tags for stage inconsistent + * types are added in the `staging` phase to level 0 quotes. Tags for types that refer to + * definitions in an outer quote are added in the `splicing` phase + * * @param body The tree that was quoted + * @param tags Term references to instances of `Type[T]` for `T`s that are used in the quote */ - case class Quote[+T <: Untyped] private[ast] (body: Tree[T])(implicit @constructorOnly src: SourceFile) + case class Quote[+T <: Untyped] private[ast] (body: Tree[T], tags: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends TermTree[T] { type ThisTree[+T <: Untyped] = Quote[T] @@ -1313,9 +1318,9 @@ object Trees { case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)(sourceFile(tree))) } - def Quote(tree: Tree)(body: Tree)(using Context): Quote = tree match { - case tree: Quote if (body eq tree.body) => tree - case _ => finalize(tree, untpd.Quote(body)(sourceFile(tree))) + def Quote(tree: Tree)(body: Tree, tags: List[Tree])(using Context): Quote = tree match { + case tree: Quote if (body eq tree.body) && (tags eq tree.tags) => tree + case _ => finalize(tree, untpd.Quote(body, tags)(sourceFile(tree))) } def Splice(tree: Tree)(expr: Tree)(using Context): Splice = tree match { case tree: Splice if (expr eq tree.expr) => tree @@ -1558,8 +1563,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) - case tree @ Quote(body) => - cpy.Quote(tree)(transform(body)(using quoteContext)) + case Quote(body, tags) => + cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags)) case tree @ Splice(expr) => cpy.Splice(tree)(transform(expr)(using spliceContext)) case tree @ Hole(isTerm, idx, args, content, tpt) => @@ -1703,8 +1708,8 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case Quote(body) => - this(x, body)(using quoteContext) + case Quote(body, tags) => + this(this(x, body)(using quoteContext), tags) case Splice(expr) => this(x, expr)(using spliceContext) case Hole(_, _, args, content, tpt) => diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index d26735bdeb05..503c1a0b1b39 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -170,8 +170,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion) - def Quote(body: Tree)(using Context): Quote = - untpd.Quote(body).withBodyType(body.tpe) + def Quote(body: Tree, tags: List[Tree])(using Context): Quote = + untpd.Quote(body, tags).withBodyType(body.tpe) def Splice(expr: Tree, tpe: Type)(using Context): Splice = untpd.Splice(expr).withType(tpe) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index dbac7ceaaf05..479d85f6e383 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -397,7 +397,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def SeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): SeqLiteral = new SeqLiteral(elems, elemtpt) def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt) def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion) - def Quote(body: Tree)(implicit src: SourceFile): Quote = new Quote(body) + def Quote(body: Tree, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags) def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr) def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree() def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree() diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 00e017430a5f..3c4c45ab254a 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -211,6 +211,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = _ private var myPicklerPhase: Phase = _ private var myInliningPhase: Phase = _ + private var myStagingPhase: Phase = _ private var mySplicingPhase: Phase = _ private var myFirstTransformPhase: Phase = _ private var myCollectNullableFieldsPhase: Phase = _ @@ -235,6 +236,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase final def inliningPhase: Phase = myInliningPhase + final def stagingPhase: Phase = myStagingPhase final def splicingPhase: Phase = mySplicingPhase final def firstTransformPhase: Phase = myFirstTransformPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase @@ -262,6 +264,7 @@ object Phases { mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) + myStagingPhase = phaseOfClass(classOf[Staging]) mySplicingPhase = phaseOfClass(classOf[Splicing]) myFirstTransformPhase = phaseOfClass(classOf[FirstTransform]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) @@ -449,6 +452,7 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase + def stagingPhase(using Context): Phase = ctx.base.stagingPhase def splicingPhase(using Context): Phase = ctx.base.splicingPhase def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 5662b17b6697..898ef2ed0c6c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -665,7 +665,7 @@ class TreePickler(pickler: TastyPickler) { pickleTree(hi) pickleTree(alias) } - case tree @ Quote(body) => + case tree @ Quote(body, Nil) => // TODO: Add QUOTE tag to TASTy assert(body.isTerm, """Quote with type should not be pickled. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 10395a488640..659d96a386ea 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1269,7 +1269,7 @@ class TreeUnpickler(reader: TastyReader, def quotedExpr(fn: Tree, args: List[Tree]): Tree = val TypeApply(_, targs) = fn: @unchecked - untpd.Quote(args.head).withBodyType(targs.head.tpe) + untpd.Quote(args.head, Nil).withBodyType(targs.head.tpe) def splicedExpr(fn: Tree, args: List[Tree]): Tree = val TypeApply(_, targs) = fn: @unchecked diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index ccef777380e1..54decf137b42 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -827,7 +827,7 @@ class Inliner(val call: tpd.Tree)(using Context): override def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = super.typedQuote(tree, pt) match - case Quote(Splice(inner)) => inner + case Quote(Splice(inner), _) => inner case tree1 => ctx.compilationUnit.needsStaging = true tree1 diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8d13bdeed68f..2a496b03e652 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1243,7 +1243,7 @@ object Parsers { } } in.nextToken() - Quote(t) + Quote(t, Nil) } else if !in.featureEnabled(Feature.symbolLiterals) then @@ -2480,7 +2480,7 @@ object Parsers { val body = if (in.token == LBRACKET) inBrackets(typ()) else stagedBlock() - Quote(body) + Quote(body, Nil) } } case NEW => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 75085469b7d2..47dac083cfce 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -726,11 +726,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case MacroTree(call) => keywordStr("macro ") ~ toTextGlobal(call) - case tree @ Quote(body) => + case tree @ Quote(body, tags) => + val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty) val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) val open = if (body.isTerm) keywordStr("{") else keywordStr("[") val close = if (body.isTerm) keywordStr("}") else keywordStr("]") - keywordStr("'") ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close + keywordStr("'") ~ tagsText ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close case Splice(expr) => val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}") diff --git a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala index feb93c3c6319..8360d8e08211 100644 --- a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala +++ b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala @@ -16,28 +16,36 @@ import dotty.tools.dotc.util.Property import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.SrcPos -/** Checks that staging level consistency holds and heals staged types . +/** Checks that staging level consistency holds and heals staged types. * * Local term references are level consistent if and only if they are used at the same level as their definition. * * Local type references can be used at the level of their definition or lower. If used used at a higher level, * it will be healed if possible, otherwise it is inconsistent. * - * Type healing consists in transforming a level inconsistent type `T` into `summon[Type[T]].Underlying`. + * Healing a type consists in replacing locally defined types defined at staging level 0 and used in higher levels. + * For each type local `T` that is defined at level 0 and used in a quote, we summon a tag `t: Type[T]`. This `t` + * tag must be defined at level 0. The tags will be listed in the `tags` of the level 0 quote (`'{ ... }`) and + * each reference to `T` will be replaced by `t.Underlying` in the body of the quote. + * + * We delay the healing of types in quotes at level 1 or higher until those quotes reach level 0. At this point + * more types will be statically known and fewer types will need to be healed. This also keeps the nested quotes + * in their original form, we do not want macro users to see any artifacts of this phase in quoted expressions + * they might inspect. + * + * Type heal example: * - * As references to types do not necessarily have an associated tree it is not always possible to replace the types directly. - * Instead we always generate a type alias for it and place it at the start of the surrounding quote. This also avoids duplication. - * For example: * '{ * val x: List[T] = List[T]() + * '{ .. T .. } * () * } * * is transformed to * - * '{ - * type t$1 = summon[Type[T]].Underlying - * val x: List[t$1] = List[t$1](); + * '{ // where `t` is a given term of type `Type[T]` + * val x: List[t.Underlying] = List[t.Underlying](); + * '{ .. t.Underlying .. } * () * } * @@ -56,11 +64,18 @@ class CrossStageSafety extends TreeMapWithStages { case tree: Quote => if (ctx.property(InAnnotation).isDefined) report.error("Cannot have a quote in an annotation", tree.srcPos) - val body1 = transformQuoteBody(tree.body, tree.span) - val stripAnnotationsDeep: TypeMap = new TypeMap: - def apply(tp: Type): Type = mapOver(tp.stripAnnots) - val bodyType1 = healType(tree.srcPos)(stripAnnotationsDeep(tree.bodyType)) - cpy.Quote(tree)(body1).withBodyType(bodyType1) + + val tree1 = + val stripAnnotationsDeep: TypeMap = new TypeMap: + def apply(tp: Type): Type = mapOver(tp.stripAnnots) + val bodyType1 = healType(tree.srcPos)(stripAnnotationsDeep(tree.bodyType)) + tree.withBodyType(bodyType1) + + if level == 0 then + val (tags, body1) = inContextWithQuoteTypeTags { transform(tree1.body)(using quoteContext) } + cpy.Quote(tree1)(body1, tags) + else + super.transform(tree1) case CancelledSplice(tree) => transform(tree) // Optimization: `${ 'x }` --> `x` @@ -74,22 +89,18 @@ class CrossStageSafety extends TreeMapWithStages { case tree @ QuotedTypeOf(body) => if (ctx.property(InAnnotation).isDefined) report.error("Cannot have a quote in an annotation", tree.srcPos) - body.tpe match - case DirectTypeOf(termRef) => - // Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x` - ref(termRef).withSpan(tree.span) - case _ => - transformQuoteBody(body, tree.span) match - case DirectTypeOf.Healed(termRef) => - // Optimization: `quoted.Type.of[@SplicedType type T = x.Underlying; T](quotes)` --> `x` - ref(termRef).withSpan(tree.span) - case transformedBody => - val quotes = transform(tree.args.head) - // `quoted.Type.of[](quotes)` --> `quoted.Type.of[](quotes)` - val TypeApply(fun, _) = tree.fun: @unchecked - if level != 0 then cpy.Apply(tree)(cpy.TypeApply(tree.fun)(fun, transformedBody :: Nil), quotes :: Nil) - else tpd.Quote(transformedBody).select(nme.apply).appliedTo(quotes).withSpan(tree.span) + if level == 0 then + val (tags, body1) = inContextWithQuoteTypeTags { transform(body)(using quoteContext) } + val quotes = transform(tree.args.head) + tags match + case tag :: Nil if body1.isType && body1.tpe =:= tag.tpe.select(tpnme.Underlying) => + tag // Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x` + case _ => + // `quoted.Type.of[]()` --> `'[].apply()` + tpd.Quote(body1, tags).select(nme.apply).appliedTo(quotes).withSpan(tree.span) + else + super.transform(tree) case _: DefDef if tree.symbol.isInlineMethod => tree @@ -137,17 +148,6 @@ class CrossStageSafety extends TreeMapWithStages { super.transform(tree) end transform - private def transformQuoteBody(body: Tree, span: Span)(using Context): Tree = { - val taggedTypes = new QuoteTypeTags(span) - val contextWithQuote = - if level == 0 then contextWithQuoteTypeTags(taggedTypes)(using quoteContext) - else quoteContext - val transformedBody = transform(body)(using contextWithQuote) - taggedTypes.getTypeTags match - case Nil => transformedBody - case tags => tpd.Block(tags, transformedBody).withSpan(body.span) - } - def transformTypeAnnotationSplices(tp: Type)(using Context) = new TypeMap { def apply(tp: Type): Type = tp match case tp: AnnotatedType => @@ -234,7 +234,7 @@ class CrossStageSafety extends TreeMapWithStages { def unapply(tree: Splice): Option[Tree] = def rec(tree: Tree): Option[Tree] = tree match case Block(Nil, expr) => rec(expr) - case Quote(inner) => Some(inner) + case Quote(inner, _) => Some(inner) case _ => None rec(tree.expr) } diff --git a/compiler/src/dotty/tools/dotc/staging/DirectTypeOf.scala b/compiler/src/dotty/tools/dotc/staging/DirectTypeOf.scala deleted file mode 100644 index 488d8ff2a88e..000000000000 --- a/compiler/src/dotty/tools/dotc/staging/DirectTypeOf.scala +++ /dev/null @@ -1,25 +0,0 @@ -package dotty.tools.dotc.staging - -import dotty.tools.dotc.ast.{tpd, untpd} -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Types._ - -object DirectTypeOf: - import tpd.* - - /** Matches `x.Underlying` and extracts the TermRef to `x` */ - def unapply(tpe: Type)(using Context): Option[TermRef] = tpe match - case tp @ TypeRef(x: TermRef, _) if tp.symbol == defn.QuotedType_splice => Some(x) - case _ => None - - object Healed: - /** Matches `{ @SplicedType type T = x.Underlying; T }` and extracts the TermRef to `x` */ - def unapply(body: Tree)(using Context): Option[TermRef] = - body match - case Block(List(tdef: TypeDef), tpt: TypeTree) => - tpt.tpe match - case tpe: TypeRef if tpe.typeSymbol == tdef.symbol => - DirectTypeOf.unapply(tdef.rhs.tpe.hiBound) - case _ => None - case _ => None diff --git a/compiler/src/dotty/tools/dotc/staging/HealType.scala b/compiler/src/dotty/tools/dotc/staging/HealType.scala index 2facb64746b3..023271960b40 100644 --- a/compiler/src/dotty/tools/dotc/staging/HealType.scala +++ b/compiler/src/dotty/tools/dotc/staging/HealType.scala @@ -25,7 +25,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { * * If `T` is a reference to a type at the wrong level, try to heal it by replacing it with * a type tag of type `quoted.Type[T]`. - * The tag is generated by an instance of `QuoteTypeTags` directly if the splice is explicit + * The tag is recorded by an instance of `QuoteTypeTags` directly if the splice is explicit * or indirectly by `tryHeal`. */ def apply(tp: Type): Type = @@ -43,11 +43,9 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { private def healTypeRef(tp: TypeRef): Type = tp.prefix match - case NoPrefix if tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => - tp case prefix: TermRef if tp.symbol.isTypeSplice => checkNotWildcardSplice(tp) - if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix) + if level == 0 then tp else getTagRef(prefix) case _: NamedType | _: ThisType | NoPrefix => if levelInconsistentRootOfPath(tp).exists then tryHeal(tp) @@ -58,7 +56,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { private object NonSpliceAlias: def unapply(tp: TypeRef)(using Context): Option[Type] = tp.underlying match - case TypeAlias(alias) if !tp.symbol.isTypeSplice && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => Some(alias) + case TypeAlias(alias) if !tp.symbol.isTypeSplice => Some(alias) case _ => None private def checkNotWildcardSplice(splice: TypeRef): Unit = @@ -78,7 +76,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { /** Try to heal reference to type `T` used in a higher level than its definition. * Returns a reference to a type tag generated by `QuoteTypeTags` that contains a - * reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`. + * reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}.Underlying`. * Emits an error if `T` cannot be healed and returns `T`. */ protected def tryHeal(tp: TypeRef): Type = { @@ -88,7 +86,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { case tp: TermRef => ctx.typer.checkStable(tp, pos, "type witness") if levelOf(tp.symbol) > 0 then tp.select(tpnme.Underlying) - else getQuoteTypeTags.getTagRef(tp) + else getTagRef(tp) case _: SearchFailureType => report.error( ctx.typer.missingArgMsg(tag, reqType, "") diff --git a/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala b/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala index d6f0a6576085..0b5032ea5a6d 100644 --- a/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala +++ b/compiler/src/dotty/tools/dotc/staging/QuoteTypeTags.scala @@ -1,52 +1,24 @@ package dotty.tools.dotc.staging -import dotty.tools.dotc.ast.{tpd, untpd} -import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.NameKinds._ import dotty.tools.dotc.core.StdNames._ -import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.util.Property -import dotty.tools.dotc.util.Spans._ -object QuoteTypeTags { +import scala.collection.mutable.LinkedHashSet - private val TaggedTypes = new Property.Key[QuoteTypeTags] +object QuoteTypeTags: - def contextWithQuoteTypeTags(taggedTypes: QuoteTypeTags)(using Context) = - ctx.fresh.setProperty(TaggedTypes, taggedTypes) + private val TaggedTypes = new Property.Key[LinkedHashSet[TermRef]] - def getQuoteTypeTags(using Context): QuoteTypeTags = - ctx.property(TaggedTypes).get -} + def inContextWithQuoteTypeTags(body: Context ?=> tpd.Tree)(using Context): (List[tpd.Tree], tpd.Tree) = + val tags = LinkedHashSet.empty[TermRef] + val transformed = body(using ctx.fresh.setProperty(TaggedTypes, tags)) + (tags.toList.map(tpd.ref(_)), transformed) -class QuoteTypeTags(span: Span)(using Context) { - import tpd.* - - private val tags = collection.mutable.LinkedHashMap.empty[TermRef, TypeDef] - - def getTagRef(spliced: TermRef): TypeRef = { - val typeDef = tags.getOrElseUpdate(spliced, mkTagSymbolAndAssignType(spliced)) - typeDef.symbol.typeRef - } - - def getTypeTags: List[TypeDef] = tags.valuesIterator.toList - - private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = { - val splicedTree = tpd.ref(spliced).withSpan(span) - val rhs = splicedTree.select(tpnme.Underlying).withSpan(span) - val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree) - val local = newSymbol( - owner = ctx.owner, - name = UniqueName.fresh(rhs.tpe.dealias.typeSymbol.name.toTypeName), - flags = Synthetic, - info = TypeAlias(splicedTree.tpe.select(tpnme.Underlying)), - coord = span).asType - local.addAnnotation(Annotation(defn.QuotedRuntime_SplicedTypeAnnot, span)) - ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local) - } -} + def getTagRef(spliced: TermRef)(using Context): Type = + ctx.property(TaggedTypes).get += spliced + spliced.select(tpnme.Underlying) diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 7ad109b67751..b4e8c3acbc5c 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -402,7 +402,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { case tree: Quote => inContext(prepQuote(tree, start)(using outerCtx)) { val body = transformTree(tree.body, start)(using quoteContext) - goQuote(cpy.Quote(tree)(body), start) + goQuote(cpy.Quote(tree)(body, Nil), start) } case tree: Splice => inContext(prepSplice(tree, start)(using outerCtx)) { diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 581b320ed9df..98bca65093fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -9,6 +9,7 @@ import Contexts._ import Symbols._ import Constants._ import ast.Trees._ +import ast.untpd import ast.TreeTypeMap import SymUtils._ import NameKinds._ @@ -27,9 +28,7 @@ import scala.annotation.constructorOnly * * Transforms top level quote * ``` - * '{ ... - * @TypeSplice type X0 = {{ 0 | .. | contentsTpe0 | .. }} - * @TypeSplice type X2 = {{ 1 | .. | contentsTpe1 | .. }} + * '{ ... * val x1: U1 = ??? * val x2: U2 = ??? * ... @@ -45,22 +44,19 @@ 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 A // with bounds that do not contain captured types + * @TypeSplice type B // 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 + * {{{ 0 | x1 | | T0 }}} // hole + * ... + * {{{ 1 | 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 - * }, + * typeHole = Seq(a, b), * 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 @@ -87,9 +83,6 @@ class PickleQuotes extends MacroTransform { assert(Inlines.inInlineMethod) case tree: Splice => assert(Inlines.inInlineMethod) - case _ : TypeDef if !Inlines.inInlineMethod => - assert(!tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot), - s"${tree.symbol} should have been removed by PickledQuotes because it has a @quoteTypeTag") case _ => override def run(using Context): Unit = @@ -100,7 +93,9 @@ class PickleQuotes extends MacroTransform { tree match case Apply(Select(quote: Quote, nme.apply), List(quotes)) => val (contents, quote1) = makeHoles(quote) - val pickled = PickleQuotes.pickle(quote1, quotes, contents) + val quote2 = encodeTypeArgs(quote1) + val contents1 = contents ::: quote.tags + val pickled = PickleQuotes.pickle(quote2, quotes, contents1) transform(pickled) // pickle quotes that are in the contents case tree: DefDef if !tree.rhs.isEmpty && tree.symbol.isInlineMethod => tree @@ -114,12 +109,12 @@ class PickleQuotes extends MacroTransform { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match case tree @ Hole(isTerm, _, _, content, _) => - if !content.isEmpty then - contents += content - val holeType = - if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe) + assert(isTerm) + assert(!content.isEmpty) + contents += content + val holeType = getTermHoleType(tree.tpe) val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType)) - if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole + cpy.Inlined(tree)(EmptyTree, Nil, hole) case tree: DefTree => val newAnnotations = tree.symbol.annotations.mapconserve { annot => annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol))) @@ -139,25 +134,6 @@ class PickleQuotes extends MacroTransform { } } - /** Remove references to local types that will not be defined in this quote */ - private def getTypeHoleType(using Context) = new TypeMap() { - override def apply(tp: Type): Type = tp match - case tp: TypeRef if tp.typeSymbol.isTypeSplice => - apply(tp.dealias) - case tp @ TypeRef(pre, _) if isLocalPath(pre) => - val hiBound = tp.typeSymbol.info match - case info: ClassInfo => info.parents.reduce(_ & _) - case info => info.hiBound - apply(hiBound) - case tp => - mapOver(tp) - - private def isLocalPath(tp: Type): Boolean = tp match - case NoPrefix => true - case tp: TermRef if !tp.symbol.is(Package) => isLocalPath(tp.prefix) - case tp => false - } - /** Remove references to local types that will not be defined in this quote */ private def getTermHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match @@ -180,14 +156,78 @@ class PickleQuotes extends MacroTransform { val holeMaker = new HoleContentExtractor val body1 = holeMaker.transform(quote.body) - val body2 = - if quote.isTypeQuote then body1 - else Inlined(Inlines.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1) - val quote1 = cpy.Quote(quote)(body2) + val quote1 = cpy.Quote(quote)(body1, quote.tags) (holeMaker.getContents(), quote1) end makeHoles + /** Encode quote tags as holes in the quote body. + * + * ```scala + * '{ ... t.Underlying ... u.Underlying ... } + * ``` + * becomes + * ```scala + * '{ + * type T = {{ 0 | .. | .. | .. }} + * type U = {{ 1 | .. | .. | .. }} + * ... T ... U ... + * } + * ``` + */ + private def encodeTypeArgs(quote: tpd.Quote)(using Context): tpd.Quote = + if quote.tags.isEmpty then quote + else + val tdefs = quote.tags.zipWithIndex.map(mkTagSymbolAndAssignType) + val typeMapping = quote.tags.map(_.tpe).zip(tdefs.map(_.symbol.typeRef)).toMap + val typeMap = new TypeMap { + override def apply(tp: Type): Type = tp match + case TypeRef(tag: TermRef, _) if tp.typeSymbol == defn.QuotedType_splice => + typeMapping.getOrElse(tag, tp) + case _ => mapOver(tp) + } + def treeMap(tree: Tree): Tree = tree match + case Select(qual, _) if tree.symbol == defn.QuotedType_splice => + typeMapping.get(qual.tpe) match + case Some(tag) => TypeTree(tag).withSpan(tree.span) + case None => tree + case _ => tree + val body1 = new TreeTypeMap(typeMap, treeMap).transform(quote.body) + cpy.Quote(quote)(Block(tdefs, body1), quote.tags) + + private def mkTagSymbolAndAssignType(typeArg: Tree, idx: Int)(using Context): TypeDef = { + val holeType = getTypeHoleType(typeArg.tpe.select(tpnme.Underlying)) + val hole = cpy.Hole(typeArg)(isTerm = false, idx, Nil, EmptyTree, TypeTree(holeType)) + val local = newSymbol( + owner = ctx.owner, + name = UniqueName.fresh(hole.tpe.dealias.typeSymbol.name.toTypeName), + flags = Synthetic, + info = TypeAlias(typeArg.tpe.select(tpnme.Underlying)), + coord = typeArg.span + ).asType + local.addAnnotation(Annotation(defn.QuotedRuntime_SplicedTypeAnnot, typeArg.span)) + ctx.typeAssigner.assignType(untpd.TypeDef(local.name, hole), local).withSpan(typeArg.span) + } + + /** Remove references to local types that will not be defined in this quote */ + private def getTypeHoleType(using Context) = new TypeMap() { + override def apply(tp: Type): Type = tp match + case tp: TypeRef if tp.typeSymbol.isTypeSplice => + apply(tp.dealias) + case tp @ TypeRef(pre, _) if isLocalPath(pre) => + val hiBound = tp.typeSymbol.info match + case info: ClassInfo => info.parents.reduce(_ & _) + case info => info.hiBound + apply(hiBound) + case tp => + mapOver(tp) + + private def isLocalPath(tp: Type): Boolean = tp match + case NoPrefix => true + case tp: TermRef if !tp.symbol.is(Package) => isLocalPath(tp.prefix) + case tp => false + } + } object PickleQuotes { @@ -286,7 +326,10 @@ object PickleQuotes { * this closure is always applied directly to the actual context and the BetaReduce phase removes it. */ def pickleAsTasty() = { - val pickleQuote = PickledQuotes.pickleQuote(body) + val body1 = + if body.isType then body + else Inlined(Inlines.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body) + val pickleQuote = PickledQuotes.pickleQuote(body1) val pickledQuoteStrings = pickleQuote match case x :: Nil => Literal(Constant(x)) case xs => tpd.mkList(xs.map(x => Literal(Constant(x))), TypeTree(defn.StringType)) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala index bd0c063cd21a..6e73d683fa2c 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -75,7 +75,7 @@ trait ReifiedReflect: .select(defn.Quotes_reflect_TypeRepr_of) .appliedToType(tpe) .appliedTo( - tpd.Quote(TypeTree(tpe)) + tpd.Quote(TypeTree(tpe), Nil) .select(nme.apply) .appliedTo(quotesTree) ) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 8f45fd94d205..741c770e2c77 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -44,7 +44,7 @@ object Splicer { * See: `Staging` */ def splice(tree: Tree, splicePos: SrcPos, spliceExpansionPos: SrcPos, classLoader: ClassLoader)(using Context): Tree = tree match { - case Quote(quotedTree) => quotedTree + case Quote(quotedTree, Nil) => quotedTree case _ => val macroOwner = newSymbol(ctx.owner, nme.MACROkw, Macro | Synthetic, defn.AnyType, coord = tree.span) try @@ -136,7 +136,7 @@ object Splicer { * See: `Staging` */ def checkValidMacroBody(tree: Tree)(using Context): Unit = tree match { - case Quote(_) => // ok + case Quote(_, Nil) => // ok case _ => type Env = Set[Symbol] @@ -155,7 +155,7 @@ object Splicer { case Block(Nil, expr) => checkIfValidArgument(expr) case Typed(expr, _) => checkIfValidArgument(expr) - case Apply(Select(Quote(body), nme.apply), _) => + case Apply(Select(Quote(body, _), nme.apply), _) => val noSpliceChecker = new TreeTraverser { def traverse(tree: Tree)(using Context): Unit = tree match case Splice(_) => @@ -203,7 +203,7 @@ object Splicer { case Typed(expr, _) => checkIfValidStaticCall(expr) - case Apply(Select(Quote(quoted), nme.apply), _) => + case Apply(Select(Quote(quoted, Nil), nme.apply), _) => // OK, canceled and warning emitted case Call(fn, args) @@ -240,7 +240,7 @@ object Splicer { override protected def interpretTree(tree: Tree)(implicit env: Env): Object = tree match { // Interpret level -1 quoted code `'{...}` (assumed without level 0 splices) - case Apply(Select(Quote(body), nme.apply), _) => + case Apply(Select(Quote(body, _), nme.apply), _) => val body1 = body match { case expr: Ident if expr.symbol.isAllOf(InlineByNameProxy) => // inline proxy for by-name parameter diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index b482e9311c74..bf7eb2dedb7d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -23,6 +23,7 @@ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.config.ScalaRelease.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.staging.QuoteTypeTags +import dotty.tools.dotc.staging.QuoteTypeTags.* import scala.annotation.constructorOnly @@ -88,7 +89,7 @@ class Splicing extends MacroTransform: tree match case tree: Quote => val body1 = QuoteTransformer().transform(tree.body)(using quoteContext) - cpy.Quote(tree)(body = body1) + cpy.Quote(tree)(body1, tree.tags) case tree: DefDef if tree.symbol.is(Inline) => // Quotes in inlined methods are only pickled after they are inlined. tree @@ -117,18 +118,6 @@ class Splicing extends MacroTransform: val newSplicedCode1 = splicer.transformSplice(tree.expr, tree.tpe, holeIdx)(using spliceContext) val newSplicedCode2 = Level0QuoteTransformer.transform(newSplicedCode1)(using spliceContext) newSplicedCode2 - case tree: TypeDef if tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => - val tp @ TypeRef(qual: TermRef, _) = tree.rhs.tpe.hiBound: @unchecked - quotedDefs += tree.symbol - val hole = typeHoles.get(qual) match - case Some (hole) => cpy.Hole(hole)(content = EmptyTree) - case None => - val holeIdx = numHoles - numHoles += 1 - val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) - typeHoles.put(qual, hole) - hole - cpy.TypeDef(tree)(rhs = hole) case _: Template => for sym <- tree.symbol.owner.info.decls do quotedDefs += sym @@ -174,14 +163,13 @@ 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 ...} }}} + * {{{ | T2 | x, X | (x$1: Expr[T1], X$1: Type[X]) => (using Quotes) ?=> '{... ${x$1} ... X$1.Underlying ...} }}} * ``` */ private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: private var refBindingMap = 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 def transformSplice(tree: tpd.Tree, tpe: Type, holeIdx: Int)(using Context): tpd.Tree = assert(level == 0) @@ -229,33 +217,23 @@ class Splicing extends MacroTransform: else super.transform(tree) case CapturedApplication(fn, argss) => transformCapturedApplication(tree, fn, argss) - case Apply(Select(Quote(body), nme.apply), quotes :: Nil) if level == 0 && body.isTerm => + case Apply(Select(Quote(body, _), nme.apply), quotes :: Nil) if level == 0 && body.isTerm => body match case _: RefTree if isCaptured(body.symbol) => capturedTerm(body) case _ => withCurrentQuote(quotes) { super.transform(tree) } - case Quote(body) if level == 0 => - val newBody = - if body.isTerm then transformLevel0QuoteContent(body)(using quoteContext) - else if containsCapturedType(body.tpe) then capturedPartTypes(body) - else body - cpy.Quote(tree)(newBody) + case tree: Quote if level == 0 => + if tree.body.isTerm then transformLevel0Quote(tree) + else if containsCapturedType(tree.body.tpe) then capturedPartTypes(tree) + else tree case _ => super.transform(tree) - private def transformLevel0QuoteContent(tree: Tree)(using Context): Tree = + private def transformLevel0Quote(quote: Quote)(using Context): Tree = // transform and collect new healed types - val old = healedTypes - healedTypes = new QuoteTypeTags(tree.span) - val tree1 = transform(tree) - val newHealedTypes = healedTypes.nn.getTypeTags - healedTypes = old - // add new healed types to the current, merge with existing healed types if necessary - if newHealedTypes.isEmpty then tree1 - else tree1 match - case Block(stats @ (x :: _), expr) if x.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => - Block(newHealedTypes ::: stats, expr) - case _ => - Block(newHealedTypes, tree1) + val (tags, body1) = inContextWithQuoteTypeTags { + transform(quote.body)(using quoteContext) + } + cpy.Quote(quote)(body1, quote.tags ::: tags) class ArgsClause(val args: List[Tree]): def isTerm: Boolean = args.isEmpty || args.head.isTerm @@ -341,35 +319,26 @@ class Splicing extends MacroTransform: .getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newQuotedTypeClassBinding(tpe)))._2 bindingSym - private def capturedPartTypes(tpt: Tree)(using Context): Tree = - val old = healedTypes - healedTypes = QuoteTypeTags(tpt.span) - val capturePartTypes = new TypeMap { - def apply(tp: Type) = tp match { - case typeRef: TypeRef if containsCapturedType(typeRef) => - val termRef = refBindingMap - .getOrElseUpdate(typeRef.symbol, (TypeTree(typeRef), newQuotedTypeClassBinding(typeRef)))._2.termRef - val tagRef = healedTypes.nn.getTagRef(termRef) - tagRef - case _ => - mapOver(tp) + private def capturedPartTypes(quote: Quote)(using Context): Tree = + val (tags, body1) = inContextWithQuoteTypeTags { + val capturePartTypes = new TypeMap { + def apply(tp: Type) = tp match { + case typeRef: TypeRef if containsCapturedType(typeRef) => + val termRef = refBindingMap + .getOrElseUpdate(typeRef.symbol, (TypeTree(typeRef), newQuotedTypeClassBinding(typeRef)))._2.termRef + val tagRef = getTagRef(termRef) + tagRef + case _ => + mapOver(tp) + } } + TypeTree(capturePartTypes(quote.body.tpe.widenTermRefExpr)) } - val captured = capturePartTypes(tpt.tpe.widenTermRefExpr) - val newHealedTypes = healedTypes.nn.getTypeTags - healedTypes = old - tpt match - case block: Block => - cpy.Block(block)(newHealedTypes ::: block.stats, TypeTree(captured)) - case _ => - if newHealedTypes.nonEmpty then - cpy.Block(tpt)(newHealedTypes, TypeTree(captured)) - else - tpt + cpy.Quote(quote)(body1, quote.tags ::: tags) private def getTagRefFor(tree: Tree)(using Context): Tree = val capturedTypeSym = capturedType(tree) - TypeTree(healedTypes.nn.getTagRef(capturedTypeSym.termRef)) + TypeTree(getTagRef(capturedTypeSym.termRef)) private def withCurrentQuote[T](newQuotes: Tree)(body: => T)(using Context): T = if level == 0 then @@ -392,7 +361,7 @@ class Splicing extends MacroTransform: Splice(closure, tpe) private def quoted(expr: Tree)(using Context): Tree = - tpd.Quote(expr).select(nme.apply).appliedTo(quotes.nn) + tpd.Quote(expr, Nil).select(nme.apply).appliedTo(quotes.nn) /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ private object reflect extends ReifiedReflect { diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 35a67e670457..43cbe80ce8c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -31,7 +31,7 @@ class Staging extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - if (ctx.phase <= splicingPhase) { + if (ctx.phase <= stagingPhase) { // Recheck that staging level consistency holds but do not heal any inconsistent types as they should already have been heald tree match { case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 6d066ff4dc76..d51f5d283946 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -20,6 +20,8 @@ import ast.{tpd, untpd} import util.Chars._ import collection.mutable import ProtoTypes._ +import staging.StagingLevel +import inlines.Inlines.inInlineMethod import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions @@ -657,6 +659,34 @@ object TreeChecker { else super.typedPackageDef(tree) + override def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = + if ctx.phase <= stagingPhase.prev then + assert(tree.tags.isEmpty, i"unexpected tags in Quote before staging phase: ${tree.tags}") + else + assert(!tree.body.isInstanceOf[untpd.Splice] || inInlineMethod, i"missed quote cancellation in $tree") + assert(!tree.body.isInstanceOf[untpd.Hole] || inInlineMethod, i"missed quote cancellation in $tree") + if StagingLevel.level != 0 then + assert(tree.tags.isEmpty, i"unexpected tags in Quote at staging level ${StagingLevel.level}: ${tree.tags}") + + for tag <- tree.tags do + assert(tag.isInstanceOf[RefTree], i"expected RefTree in Quote but was: $tag") + + val tree1 = super.typedQuote(tree, pt) + for tag <- tree.tags do + assert(tag.typeOpt.derivesFrom(defn.QuotedTypeClass), i"expected Quote tag to be of type `Type` but was: ${tag.tpe}") + + tree1 match + case Quote(body, targ :: Nil) if body.isType => + assert(!(body.tpe =:= targ.tpe.select(tpnme.Underlying)), i"missed quote cancellation in $tree1") + case _ => + + tree1 + + override def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = + if stagingPhase <= ctx.phase then + assert(!tree.expr.isInstanceOf[untpd.Quote] || inInlineMethod, i"missed quote cancellation in $tree") + super.typedSplice(tree, pt) + override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = { val tree1 @ Hole(isTerm, _, args, content, tpt) = super.typedHole(tree, pt): @unchecked diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index b18cc55364d8..7238756454b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -312,7 +312,7 @@ object SpaceEngine { def isIrrefutableQuotedPattern(unapp: tpd.Tree, implicits: List[tpd.Tree], pt: Type)(using Context): Boolean = { implicits.headOption match // pattern '{ $x: T } - case Some(tpd.Apply(tpd.Select(tpd.Quote(tpd.TypeApply(fn, List(tpt))), nme.apply), _)) + case Some(tpd.Apply(tpd.Select(tpd.Quote(tpd.TypeApply(fn, List(tpt)), _), nme.apply), _)) if unapp.symbol.owner.eq(defn.QuoteMatching_ExprMatchModule) && fn.symbol.eq(defn.QuotedRuntimePatterns_patternHole) => pt <:< defn.QuotedExprClass.typeRef.appliedTo(tpt.tpe) diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index 3a5ea05726a5..8b8858e16596 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -61,7 +61,7 @@ trait QuotesAndSplices { // TODO typecheck directly (without `exprQuote`) val exprQuoteTree = untpd.Apply(untpd.ref(defn.QuotedRuntime_exprQuote.termRef), tree.body) val quotedExpr = typedApply(exprQuoteTree, pt)(using quoteContext) match - case Apply(TypeApply(fn, tpt :: Nil), quotedExpr :: Nil) => untpd.Quote(quotedExpr).withBodyType(tpt.tpe) + case Apply(TypeApply(fn, tpt :: Nil), quotedExpr :: Nil) => untpd.Quote(quotedExpr, Nil).withBodyType(tpt.tpe) makeInlineable(quotedExpr.select(nme.apply).appliedTo(quotes).withSpan(tree.span)) } @@ -75,7 +75,7 @@ trait QuotesAndSplices { record("typedSplice") checkSpliceOutsideQuote(tree) tree.expr match { - case untpd.Quote(innerExpr) if innerExpr.isTerm => + case untpd.Quote(innerExpr, Nil) if innerExpr.isTerm => report.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.srcPos) return typed(innerExpr, pt) case _ => @@ -442,7 +442,7 @@ trait QuotesAndSplices { val quoteClass = if (quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass val quotedPattern = - if (quoted.isTerm) tpd.Quote(shape).select(nme.apply).appliedTo(quotes) + if (quoted.isTerm) tpd.Quote(shape, Nil).select(nme.apply).appliedTo(quotes) else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape).appliedTo(quotes) val matchModule = if quoted.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index f8cfd4ca3ae0..ddc7a02fb4fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -98,7 +98,8 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = assertTyped(tree) val body1 = typed(tree.body, tree.bodyType)(using quoteContext) - untpd.cpy.Quote(tree)(body1).withType(tree.typeOpt) + for tag <- tree.tags do assertTyped(tag) + untpd.cpy.Quote(tree)(body1, tree.tags).withType(tree.typeOpt) override def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = assertTyped(tree)