diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 046b649941b1..8e4989f13c3d 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -154,11 +154,13 @@ object CompilationUnit { var containsCaptureChecking = false var containsMacroAnnotation = false def traverse(tree: Tree)(using Context): Unit = { - if (tree.symbol.isQuote) - containsQuote = true if tree.symbol.is(Flags.Inline) then containsInline = true tree match + case tpd.Quote(_) => + containsQuote = true + case tree: tpd.Apply if tree.symbol == defn.QuotedTypeModule_of => + containsQuote = true case Import(qual, selectors) => tpd.languageImport(qual) match case Some(prefix) => diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index c1dd78451bae..d6f9e0d24c91 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1986,13 +1986,13 @@ object desugar { trees foreach collect case Block(Nil, expr) => collect(expr) - case Quote(expr) => + case Quote(body) => new UntypedTreeTraverser { def traverse(tree: untpd.Tree)(using Context): Unit = tree match { case Splice(expr) => collect(expr) case _ => traverseChildren(tree) } - }.traverse(expr) + }.traverse(body) case CapturingTypeTree(refs, parent) => collect(parent) case _ => diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index c2147b6af2d3..5b409b6fec9a 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -1026,33 +1026,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case t => assert(t.span.exists, i"$t") } - /** Extractors for quotes */ - object Quoted { + object QuotedTypeOf { /** Extracts the content of a quoted tree. * The result can be the contents of a term or type quote, which * will return a term or type tree respectively. */ def unapply(tree: tpd.Apply)(using Context): Option[tpd.Tree] = - if tree.symbol == defn.QuotedRuntime_exprQuote then - // quoted.runtime.Expr.quote[T]() - Some(tree.args.head) - else if tree.symbol == defn.QuotedTypeModule_of then + if tree.symbol == defn.QuotedTypeModule_of then // quoted.Type.of[](quotes) val TypeApply(_, body :: _) = tree.fun: @unchecked Some(body) else None } - /** Extractors for splices */ - object Spliced { - /** Extracts the content of a spliced expression tree. - * The result can be the contents of a term splice, which - * will return a term tree. - */ - def unapply(tree: tpd.Apply)(using Context): Option[tpd.Tree] = - if tree.symbol.isExprSplice then Some(tree.args.head) else None - } - /** Extractors for type splices */ object SplicedType { /** Extracts the content of a spliced type tree. diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 1c1e80922c05..77583a9a77e2 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -17,6 +17,7 @@ import annotation.unchecked.uncheckedVariance import annotation.constructorOnly import compiletime.uninitialized import Decorators._ +import staging.StagingLevel.* object Trees { @@ -677,6 +678,60 @@ object Trees { override def isType = expansion.isType } + /** A tree representing a quote `'{ body }` or `'[ body ]`. + * `Quote`s are created by the `Parser`. In typer they can be typed as a + * `Quote` with a known `tpt` or desugared and typed as a quote pattern. + * + * `Quotes` are checked and transformed in the `staging`, `splicing` and `pickleQuotes` + * phases. After `pickleQuotes` phase, the only quotes that exist are in `inline` + * methods. These are dropped when we remove the inline method implementations. + * + * Type quotes `'[body]` from the parser are desugared into quote patterns (using a `Type.of[T]]`) + * 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]]`. + * + * @param body The tree that was quoted + */ + case class Quote[+T <: Untyped] private[ast] (body: Tree[T])(implicit @constructorOnly src: SourceFile) + extends TermTree[T] { + type ThisTree[+T <: Untyped] = Quote[T] + + /** Is this a type quote `'[tpe]' */ + def isTypeQuote = body.isType + + /** Type of the quoted expression as seen from outside the quote */ + def bodyType(using Context): Type = + val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]` + val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]` + exprType.argInfos.head // T + + /** Set the type of the body of the quote */ + def withBodyType(tpe: Type)(using Context): Quote[Type] = + val exprType = // `Expr[T]` or `Type[T]` + if body.isTerm then defn.QuotedExprClass.typeRef.appliedTo(tpe) + else defn.QuotedTypeClass.typeRef.appliedTo(tpe) + val quoteType = // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]` + defn.FunctionType(1, isContextual = true) + .appliedTo(defn.QuotesClass.typeRef, exprType) + withType(quoteType) + } + + /** A tree representing a splice `${ expr }` + * + * `Splice`s are created by the `Parser`. In typer they can be typed as a + * `Splice` with a known `tpt` or desugared and typed as a quote pattern holes. + * + * `Splice` are checked and transformed in the `staging` and `splicing` phases. + * After `splicing` phase, the only splices that exist are in `inline` + * methods. These are dropped when we remove the inline method implementations. + * + * @param expr The tree that was spliced + */ + case class Splice[+T <: Untyped] private[ast] (expr: Tree[T])(implicit @constructorOnly src: SourceFile) + extends TermTree[T] { + type ThisTree[+T <: Untyped] = Splice[T] + } + /** A type tree that represents an existing or inferred type */ case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile) extends DenotingTree[T] with TypTree[T] { @@ -1087,6 +1142,8 @@ object Trees { type SeqLiteral = Trees.SeqLiteral[T] type JavaSeqLiteral = Trees.JavaSeqLiteral[T] type Inlined = Trees.Inlined[T] + type Quote = Trees.Quote[T] + type Splice = Trees.Splice[T] type TypeTree = Trees.TypeTree[T] type InferredTypeTree = Trees.InferredTypeTree[T] type SingletonTypeTree = Trees.SingletonTypeTree[T] @@ -1257,6 +1314,14 @@ 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 Splice(tree: Tree)(expr: Tree)(using Context): Splice = tree match { + case tree: Splice if (expr eq tree.expr) => tree + case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree))) + } def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match { case tree: SingletonTypeTree if (ref eq tree.ref) => tree case _ => finalize(tree, untpd.SingletonTypeTree(ref)(sourceFile(tree))) @@ -1494,6 +1559,10 @@ 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 tree @ Splice(expr) => + cpy.Splice(tree)(transform(expr)(using spliceContext)) case tree @ Hole(_, _, args, content, tpt) => cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt)) case _ => @@ -1635,6 +1704,10 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) + case Quote(body) => + this(x, body)(using quoteContext) + case Splice(expr) => + this(x, expr)(using spliceContext) case Hole(_, _, args, content, tpt) => this(this(this(x, args), content), tpt) case _ => diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index c6e0bc581b59..3a957c8f4612 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -170,6 +170,12 @@ 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 Splice(expr: Tree, tpe: Type)(using Context): Splice = + untpd.Splice(expr).withType(tpe) + def TypeTree(tp: Type, inferred: Boolean = false)(using Context): TypeTree = (if inferred then untpd.InferredTypeTree() else untpd.TypeTree()).withType(tp) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index a262c3658399..30f58fba44ec 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -111,10 +111,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def isType: Boolean = !isTerm } case class Throw(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree - case class Quote(quoted: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree - case class Splice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree { - def isInBraces: Boolean = span.end != expr.span.end - } case class ForYield(enums: List[Tree], expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree case class ForDo(enums: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree case class GenFrom(pat: Tree, expr: Tree, checkMode: GenCheckMode)(implicit @constructorOnly src: SourceFile) extends Tree @@ -401,6 +397,8 @@ 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 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() def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref) @@ -622,14 +620,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: Throw if expr eq tree.expr => tree case _ => finalize(tree, untpd.Throw(expr)(tree.source)) } - def Quote(tree: Tree)(quoted: Tree)(using Context): Tree = tree match { - case tree: Quote if quoted eq tree.quoted => tree - case _ => finalize(tree, untpd.Quote(quoted)(tree.source)) - } - def Splice(tree: Tree)(expr: Tree)(using Context): Tree = tree match { - case tree: Splice if expr eq tree.expr => tree - case _ => finalize(tree, untpd.Splice(expr)(tree.source)) - } def ForYield(tree: Tree)(enums: List[Tree], expr: Tree)(using Context): TermTree = tree match { case tree: ForYield if (enums eq tree.enums) && (expr eq tree.expr) => tree case _ => finalize(tree, untpd.ForYield(enums, expr)(tree.source)) @@ -711,10 +701,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.Tuple(tree)(transform(trees)) case Throw(expr) => cpy.Throw(tree)(transform(expr)) - case Quote(t) => - cpy.Quote(tree)(transform(t)) - case Splice(expr) => - cpy.Splice(tree)(transform(expr)) case ForYield(enums, expr) => cpy.ForYield(tree)(transform(enums), transform(expr)) case ForDo(enums, body) => @@ -772,10 +758,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(x, trees) case Throw(expr) => this(x, expr) - case Quote(t) => - this(x, t) - case Splice(expr) => - this(x, expr) case ForYield(enums, expr) => this(this(x, enums), expr) case ForDo(enums, body) => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 8a396921f32b..5662b17b6697 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -665,6 +665,26 @@ class TreePickler(pickler: TastyPickler) { pickleTree(hi) pickleTree(alias) } + case tree @ Quote(body) => + // TODO: Add QUOTE tag to TASTy + assert(body.isTerm, + """Quote with type should not be pickled. + |Quote with type should only exists after staging phase at staging level 0.""".stripMargin) + pickleTree( + // scala.quoted.runtime.Expr.quoted[]() + ref(defn.QuotedRuntime_exprQuote) + .appliedToType(tree.bodyType) + .appliedTo(body) + .withSpan(tree.span) + ) + case Splice(expr) => + pickleTree( // TODO: Add SPLICE tag to TASTy + // scala.quoted.runtime.Expr.splice[]() + ref(defn.QuotedRuntime_exprSplice) + .appliedToType(tree.tpe) + .appliedTo(expr) + .withSpan(tree.span) + ) case Hole(_, idx, args, _, tpt) => writeByte(HOLE) withLength { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 428047d96e0c..10395a488640 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1267,6 +1267,21 @@ class TreeUnpickler(reader: TastyReader, res.withAttachment(SuppressedApplyToNone, ()) else res + def quotedExpr(fn: Tree, args: List[Tree]): Tree = + val TypeApply(_, targs) = fn: @unchecked + untpd.Quote(args.head).withBodyType(targs.head.tpe) + + def splicedExpr(fn: Tree, args: List[Tree]): Tree = + val TypeApply(_, targs) = fn: @unchecked + Splice(args.head, targs.head.tpe) + + def nestedSpliceExpr(fn: Tree, args: List[Tree]): Tree = + fn match + case Apply(TypeApply(_, targs), _ :: Nil) => // nestedSplice[T](quotes)(expr) + Splice(args.head, targs.head.tpe) + case _ => // nestedSplice[T](quotes) + tpd.Apply(fn, args) + def simplifyLub(tree: Tree): Tree = tree.overwriteType(tree.tpe.simplified) tree @@ -1283,6 +1298,9 @@ class TreeUnpickler(reader: TastyReader, val fn = readTree() val args = until(end)(readTree()) if fn.symbol.isConstructor then constructorApply(fn, args) + else if fn.symbol == defn.QuotedRuntime_exprQuote then quotedExpr(fn, args) + else if fn.symbol == defn.QuotedRuntime_exprSplice then splicedExpr(fn, args) + else if fn.symbol == defn.QuotedRuntime_exprNestedSplice then nestedSpliceExpr(fn, args) else tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTree(), until(end)(readTpt())) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index f4612e49ccac..ccef777380e1 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -23,7 +23,7 @@ import util.Spans.Span import dotty.tools.dotc.transform.Splicer import dotty.tools.dotc.transform.BetaReduce import quoted.QuoteUtils -import staging.StagingLevel +import staging.StagingLevel.{level, spliceContext} import scala.annotation.constructorOnly /** General support for inlining */ @@ -815,30 +815,31 @@ class Inliner(val call: tpd.Tree)(using Context): super.typedValDef(vdef1, sym) override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = - def cancelQuotes(tree: Tree): Tree = - tree match - case Quoted(Spliced(inner)) => inner - case _ => tree val locked = ctx.typerState.ownedVars - val res = cancelQuotes(constToLiteral(BetaReduce(super.typedApply(tree, pt)))) match { - case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice - && StagingLevel.level == 0 - && !hasInliningErrors => - val expanded = expandMacro(res.args.head, tree.srcPos) - transform.TreeChecker.checkMacroGeneratedTree(res, expanded) - typedExpr(expanded) // Inline calls and constant fold code generated by the macro - case res => - specializeEq(inlineIfNeeded(res, pt, locked)) - } - res + specializeEq(inlineIfNeeded(constToLiteral(BetaReduce(super.typedApply(tree, pt))), pt, locked)) override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = val locked = ctx.typerState.ownedVars val tree1 = inlineIfNeeded(constToLiteral(BetaReduce(super.typedTypeApply(tree, pt))), pt, locked) - if tree1.symbol.isQuote then + if tree1.symbol == defn.QuotedTypeModule_of then ctx.compilationUnit.needsStaging = true tree1 + override def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = + super.typedQuote(tree, pt) match + case Quote(Splice(inner)) => inner + case tree1 => + ctx.compilationUnit.needsStaging = true + tree1 + + override def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = + super.typedSplice(tree, pt) match + case tree1 @ Splice(expr) if level == 0 && !hasInliningErrors => + val expanded = expandMacro(expr, tree1.srcPos) + transform.TreeChecker.checkMacroGeneratedTree(tree1, expanded) + typedExpr(expanded) // Inline calls and constant fold code generated by the macro + case tree1 => tree1 + override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = val tree1 = if tree.isInline then @@ -1029,9 +1030,9 @@ class Inliner(val call: tpd.Tree)(using Context): } private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = { - assert(StagingLevel.level == 0) + assert(level == 0) val inlinedFrom = enclosingInlineds.last - val dependencies = macroDependencies(body) + val dependencies = macroDependencies(body)(using spliceContext) val suspendable = ctx.compilationUnit.isSuspendable if dependencies.nonEmpty && !ctx.reporter.errorsReported then for sym <- dependencies do @@ -1061,28 +1062,12 @@ class Inliner(val call: tpd.Tree)(using Context): */ private def macroDependencies(tree: Tree)(using Context) = new TreeAccumulator[List[Symbol]] { - private var level = -1 override def apply(syms: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] = - if level != -1 then foldOver(syms, tree) - else tree match { - case tree: RefTree if tree.isTerm && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => + tree match { + case tree: RefTree if tree.isTerm && level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => foldOver(tree.symbol :: syms, tree) - case Quoted(body) => - level += 1 - try apply(syms, body) - finally level -= 1 - case Spliced(body) => - level -= 1 - try apply(syms, body) - finally level += 1 - case SplicedType(body) => - level -= 1 - try apply(syms, body) - finally level += 1 - case _: TypTree => - syms - case _ => - foldOver(syms, tree) + case _: TypTree => syms + case _ => foldOver(syms, tree) } }.apply(Nil, tree) end Inliner diff --git a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala index 33bc5a7ef10e..060c8d21f390 100644 --- a/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/inlines/PrepareInlineable.scala @@ -86,14 +86,7 @@ object PrepareInlineable { } override def transform(tree: Tree)(using Context): Tree = - inContext(stagingContext(tree)) { - postTransform(super.transform(preTransform(tree))) - } - - private def stagingContext(tree: Tree)(using Context): Context = tree match - case tree: Apply if tree.symbol.isQuote => StagingLevel.quoteContext - case tree: Apply if tree.symbol.isExprSplice => StagingLevel.spliceContext - case _ => ctx + postTransform(super.transform(preTransform(tree))) } /** Direct approach: place the accessor with the accessed symbol. This has the @@ -154,7 +147,7 @@ object PrepareInlineable { val qual = qualifier(refPart) inlining.println(i"adding receiver passing inline accessor for $tree/$refPart -> (${qual.tpe}, $refPart: ${refPart.getClass}, $argss%, %") - // Need to dealias in order to cagtch all possible references to abstracted over types in + // Need to dealias in order to catch all possible references to abstracted over types in // substitutions val dealiasMap = new TypeMap { def apply(t: Type) = mapOver(t.dealias) @@ -290,7 +283,7 @@ object PrepareInlineable { if (inlined.is(Macro) && !ctx.isAfterTyper) { def checkMacro(tree: Tree): Unit = tree match { - case Spliced(code) => + case Splice(code) => if (code.symbol.flags.is(Inline)) report.error("Macro cannot be implemented with an `inline` method", code.srcPos) Splicer.checkValidMacroBody(code) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 15a639743c15..324c3037d8ce 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2494,10 +2494,10 @@ object Parsers { case QUOTE => atSpan(in.skipToken()) { withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) { - Quote { + val body = if (in.token == LBRACKET) inBrackets(typ()) else stagedBlock() - } + Quote(body) } } case NEW => diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 014e5ddf0d66..93de33778750 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -432,10 +432,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { changePrec (GlobalPrec) { keywordStr("throw ") ~ toText(args.head) } - else if (!printDebug && fun.hasType && fun.symbol == defn.QuotedRuntime_exprQuote) - keywordStr("'{") ~ toTextGlobal(args, ", ") ~ keywordStr("}") - else if (!printDebug && fun.hasType && fun.symbol.isExprSplice) - keywordStr("${") ~ toTextGlobal(args, ", ") ~ keywordStr("}") else toTextLocal(fun) ~ "(" @@ -716,14 +712,18 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } case Number(digits, kind) => digits - case Quote(tree) if tree.isTerm => - keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") - case Splice(tree) => - keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}") case Thicket(trees) => "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case MacroTree(call) => keywordStr("macro ") ~ toTextGlobal(call) + case tree @ Quote(body) => + 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 + case Splice(expr) => + val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) + keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}") case Hole(isTermHole, idx, args, content, tpt) => val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]") val argsText = toTextGlobal(args, ", ") diff --git a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala index a98284f4078d..c9a77dbfa151 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Interpreter.scala @@ -24,7 +24,6 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.TypeErasure import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.quoted._ -import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.typer.ImportInfo.withRootImports import dotty.tools.dotc.util.SrcPos import dotty.tools.dotc.reporting.Message diff --git a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala index 400a846194e7..032067eca482 100644 --- a/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala +++ b/compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala @@ -10,7 +10,6 @@ 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.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.staging.QuoteTypeTags.* import dotty.tools.dotc.util.Property @@ -51,10 +50,50 @@ class CrossStageSafety extends TreeMapWithStages { override def transform(tree: Tree)(using Context): Tree = if (tree.source != ctx.source && tree.source.exists) transform(tree)(using ctx.withSource(tree.source)) - else if !isInQuoteOrSplice then - checkAnnotations(tree) - super.transform(tree) - else tree match { + else tree match + case CancelledQuote(tree) => + transform(tree) // Optimization: `'{ $x }` --> `x` + 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) + + case CancelledSplice(tree) => + transform(tree) // Optimization: `${ 'x }` --> `x` + case tree: Splice => + val body1 = transform(tree.expr)(using spliceContext) + val tpe1 = + if level == 0 then tree.tpe + else healType(tree.srcPos)(tree.tpe.widenTermRefExpr) + untpd.cpy.Splice(tree)(body1).withType(tpe1) + + 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) + + case _ if !inQuoteOrSpliceScope => + checkAnnotations(tree) // Check quotes in annotations + super.transform(tree) + case _: TypeTree => val tp1 = transformTypeAnnotationSplices(tree.tpe) val healedType = healType(tree.srcPos)(tp1) @@ -95,79 +134,17 @@ class CrossStageSafety extends TreeMapWithStages { super.transform(tree) case _ => super.transform(tree) - } - - /** Transform quoted trees while maintaining level correctness */ - override protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = { - val taggedTypes = new QuoteTypeTags(quote.span) - - if (ctx.property(InAnnotation).isDefined) - report.error("Cannot have a quote in an annotation", quote.srcPos) - - val stripAnnotsDeep: TypeMap = new TypeMap: - def apply(tp: Type): Type = mapOver(tp.stripAnnots) - - def transformBody() = - 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) - - if body.isTerm then - val transformedBody = transformBody() - // `quoted.runtime.Expr.quote[T]()` --> `quoted.runtime.Expr.quote[T2]()` - val TypeApply(fun, targs) = quote.fun: @unchecked - val targs2 = targs.map(targ => TypeTree(healType(quote.fun.srcPos)(stripAnnotsDeep(targ.tpe)))) - cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, targs2), transformedBody :: Nil) - else - body.tpe match - case DirectTypeOf(termRef) => - // Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x` - ref(termRef).withSpan(quote.span) - case _ => - transformBody() match - case DirectTypeOf.Healed(termRef) => - // Optimization: `quoted.Type.of[@SplicedType type T = x.Underlying; T](quotes)` --> `x` - ref(termRef).withSpan(quote.span) - case transformedBody => - val quotes = quote.args.mapConserve(transform) - // `quoted.Type.of[](quotes)` --> `quoted.Type.of[](quotes)` - val TypeApply(fun, _) = quote.fun: @unchecked - cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, transformedBody :: Nil), quotes) - - } - - /** Transform splice - * - If inside a quote, transform the contents of the splice. - * - If inside inlined code, expand the macro code. - * - If inside of a macro definition, check the validity of the macro. - */ - protected def transformSplice(body: Tree, splice: Apply)(using Context): Tree = { - val body1 = transform(body)(using spliceContext) - splice.fun match { - case fun @ TypeApply(_, _ :: Nil) => - // Type of the splice itself must also be healed - // `quoted.runtime.Expr.quote[F[T]](... T ...)` --> `internal.Quoted.expr[F[$t]](... T ...)` - val tp = healType(splice.srcPos)(splice.tpe.widenTermRefExpr) - cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil) - case f @ Apply(fun @ TypeApply(_, _), quotes :: Nil) => - // Type of the splice itself must also be healed - // `quoted.runtime.Expr.quote[F[T]](... T ...)` --> `internal.Quoted.expr[F[$t]](... T ...)` - val tp = healType(splice.srcPos)(splice.tpe.widenTermRefExpr) - cpy.Apply(splice)(cpy.Apply(f)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), quotes :: Nil), body1 :: Nil) - } - } - - protected def transformSpliceType(body: Tree, splice: Select)(using Context): Tree = { - val body1 = transform(body)(using spliceContext) - if ctx.reporter.hasErrors then - splice - else - val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef]) - ref(tagRef).withSpan(splice.span) + 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 { @@ -243,4 +220,20 @@ class CrossStageSafety extends TreeMapWithStages { | - but the access is at level $level.$hint""", pos) tp } + + private object CancelledQuote: + def unapply(tree: Quote): Option[Tree] = + def rec(tree: Tree): Option[Tree] = tree match + case Block(Nil, expr) => rec(expr) + case Splice(inner) => Some(inner) + case _ => None + rec(tree.body) + + private object CancelledSplice: + 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 _ => None + rec(tree.expr) } diff --git a/compiler/src/dotty/tools/dotc/staging/HealType.scala b/compiler/src/dotty/tools/dotc/staging/HealType.scala index 50c2584b2fd0..2facb64746b3 100644 --- a/compiler/src/dotty/tools/dotc/staging/HealType.scala +++ b/compiler/src/dotty/tools/dotc/staging/HealType.scala @@ -7,7 +7,6 @@ import dotty.tools.dotc.core.Flags._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.staging.QuoteTypeTags.* import dotty.tools.dotc.transform.SymUtils._ @@ -64,7 +63,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap { private def checkNotWildcardSplice(splice: TypeRef): Unit = splice.prefix.termSymbol.info.argInfos match - case (tb: TypeBounds) :: _ => report.error(em"Cannot splice $splice because it is a wildcard type", pos) + case (tb: TypeBounds) :: _ => report.error(em"Cannot stage $splice because it is an alias to a wildcard type", pos) case _ => /** Return the root of this path if it is a variable defined in a previous level. diff --git a/compiler/src/dotty/tools/dotc/staging/QuoteContext.scala b/compiler/src/dotty/tools/dotc/staging/QuoteContext.scala deleted file mode 100644 index 8e25bba7110c..000000000000 --- a/compiler/src/dotty/tools/dotc/staging/QuoteContext.scala +++ /dev/null @@ -1,34 +0,0 @@ -package dotty.tools.dotc.staging - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.util.Property -import dotty.tools.dotc.staging.StagingLevel.* - -object QuoteContext { - - /** A key to be used in a context property that tracks the quotation stack. - * Stack containing the Quotes references received by the surrounding quotes. - */ - private val QuotesStack = new Property.Key[List[tpd.Tree]] - - /** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */ - def pushQuotes(quotes: tpd.Tree)(using Context): Context = - val old = ctx.property(QuotesStack).getOrElse(List.empty) - quoteContext.setProperty(QuotesStack, quotes :: old) - - /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. - * The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice. - */ - def popQuotes()(using Context): (Option[tpd.Tree], Context) = - val ctx1 = spliceContext - val head = - ctx.property(QuotesStack) match - case Some(x :: xs) => - ctx1.setProperty(QuotesStack, xs) - Some(x) - case _ => - None // Splice at level 0 or lower - (head, ctx1) -} diff --git a/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala b/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala index 4704501e38ff..05b3efab408c 100644 --- a/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala +++ b/compiler/src/dotty/tools/dotc/staging/StagingLevel.scala @@ -31,6 +31,10 @@ object StagingLevel { def spliceContext(using Context): FreshContext = ctx.fresh.setProperty(LevelKey, level - 1) + /** If we are inside a quote or a splice */ + def inQuoteOrSpliceScope(using Context): Boolean = + ctx.property(LevelKey).isDefined + /** The quotation level of the definition of the locally defined symbol */ def levelOf(sym: Symbol)(using Context): Int = ctx.property(LevelOfKey) match diff --git a/compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala b/compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala index adaacafa764a..674dfff2f642 100644 --- a/compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala +++ b/compiler/src/dotty/tools/dotc/staging/TreeMapWithStages.scala @@ -6,7 +6,6 @@ import dotty.tools.dotc.config.Printers.staging import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.util.Property import dotty.tools.dotc.staging.StagingLevel.* import scala.collection.mutable @@ -15,77 +14,11 @@ import scala.collection.mutable abstract class TreeMapWithStages extends TreeMapWithImplicits { import tpd._ - /** If we are inside a quote or a splice */ - private[this] var inQuoteOrSplice = false - - /** If we are inside a quote or a splice */ - protected def isInQuoteOrSplice: Boolean = inQuoteOrSplice - - /** Transform the quote `quote` which contains the quoted `body`. - * - * - `quoted.runtime.Expr.quote[T]()` --> `quoted.runtime.Expr.quote[T]()` - * - `quoted.Type.of[](quotes)` --> `quoted.Type.of[](quotes)` - */ - protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = - if body.isTerm then - cpy.Apply(quote)(quote.fun, body :: Nil) - else - val TypeApply(fun, _) = quote.fun: @unchecked - cpy.Apply(quote)(cpy.TypeApply(quote.fun)(fun, body :: Nil), quote.args) - - /** Transform the expression splice `splice` which contains the spliced `body`. */ - protected def transformSplice(body: Tree, splice: Apply)(using Context): Tree - - /** Transform the type splice `splice` which contains the spliced `body`. */ - protected def transformSpliceType(body: Tree, splice: Select)(using Context): Tree - override def transform(tree: Tree)(using Context): Tree = if (tree.source != ctx.source && tree.source.exists) transform(tree)(using ctx.withSource(tree.source)) - else reporting.trace(i"StagingTransformer.transform $tree at $level", staging, show = true) { - def dropEmptyBlocks(tree: Tree): Tree = tree match { - case Block(Nil, expr) => dropEmptyBlocks(expr) - case _ => tree - } - + else reporting.trace(i"TreeMapWithStages.transform $tree at $level", staging, show = true) { tree match { - case Apply(Select(Quoted(quotedTree), _), _) if quotedTree.isType => - dropEmptyBlocks(quotedTree) match - case SplicedType(t) => - // Optimization: `quoted.Type.of[x.Underlying]` --> `x` - transform(t) - case _ => - super.transform(tree) - - case tree @ Quoted(quotedTree) => - val old = inQuoteOrSplice - inQuoteOrSplice = true - try dropEmptyBlocks(quotedTree) match { - case Spliced(t) => - // Optimization: `'{ $x }` --> `x` - // and adapt the refinement of `Quotes { type reflect: ... } ?=> Expr[T]` - transform(t).asInstance(tree.tpe) - case _ => transformQuotation(quotedTree, tree) - } - finally inQuoteOrSplice = old - - case tree @ Spliced(splicedTree) => - val old = inQuoteOrSplice - inQuoteOrSplice = true - try dropEmptyBlocks(splicedTree) match { - case Quoted(t) => - // Optimization: `${ 'x }` --> `x` - transform(t) - case _ => transformSplice(splicedTree, tree) - } - finally inQuoteOrSplice = old - - case tree @ SplicedType(splicedTree) => - val old = inQuoteOrSplice - inQuoteOrSplice = true - try transformSpliceType(splicedTree, tree) - finally inQuoteOrSplice = old - case Block(stats, _) => val defSyms = stats.collect { case defTree: DefTree => defTree.symbol } super.transform(tree)(using symbolsInCurrentLevel(defSyms)) diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index d6b7f3141b96..10f73fa94e08 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -12,7 +12,6 @@ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.inlines.Inlines import dotty.tools.dotc.ast.TreeMapWithImplicits import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer -import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel import scala.collection.mutable.ListBuffer @@ -46,10 +45,6 @@ class Inlining extends MacroTransform { new TreeTraverser { def traverse(tree: Tree)(using Context): Unit = tree match - case _: GenericApply if tree.symbol.isQuote => - traverseChildren(tree)(using StagingLevel.quoteContext) - case _: GenericApply if tree.symbol.isExprSplice => - traverseChildren(tree)(using StagingLevel.spliceContext) case tree: RefTree if !Inlines.inInlineMethod && StagingLevel.level == 0 => assert(!tree.symbol.isInlineMethod, tree.show) case _ => @@ -98,10 +93,6 @@ class Inlining extends MacroTransform { val tree1 = super.transform(tree) if tree1.tpe.isError then tree1 else Inlines.inlineCall(tree1) - case _: GenericApply if tree.symbol.isQuote => - super.transform(tree)(using StagingLevel.quoteContext) - case _: GenericApply if tree.symbol.isExprSplice => - super.transform(tree)(using StagingLevel.spliceContext) case _: PackageDef => super.transform(tree) match case tree1: PackageDef => @@ -113,7 +104,8 @@ class Inlining extends MacroTransform { case _ => tree1 case tree1 => tree1 case _ => - super.transform(tree) + if tree.isType then tree + else super.transform(tree) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index d4dd911241d3..7ad109b67751 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -5,6 +5,7 @@ package transform import core._ import Contexts._, Phases._, Symbols._, Decorators._ import Flags.PackageVal +import staging.StagingLevel.* /** A MegaPhase combines a number of mini-phases which are all executed in * a single tree traversal. @@ -66,6 +67,8 @@ object MegaPhase { def prepareForTry(tree: Try)(using Context): Context = ctx def prepareForSeqLiteral(tree: SeqLiteral)(using Context): Context = ctx def prepareForInlined(tree: Inlined)(using Context): Context = ctx + def prepareForQuote(tree: Quote)(using Context): Context = ctx + def prepareForSplice(tree: Splice)(using Context): Context = ctx def prepareForTypeTree(tree: TypeTree)(using Context): Context = ctx def prepareForBind(tree: Bind)(using Context): Context = ctx def prepareForAlternative(tree: Alternative)(using Context): Context = ctx @@ -100,6 +103,8 @@ object MegaPhase { def transformTry(tree: Try)(using Context): Tree = tree def transformSeqLiteral(tree: SeqLiteral)(using Context): Tree = tree def transformInlined(tree: Inlined)(using Context): Tree = tree + def transformQuote(tree: Quote)(using Context): Tree = tree + def transformSplice(tree: Splice)(using Context): Tree = tree def transformTypeTree(tree: TypeTree)(using Context): Tree = tree def transformBind(tree: Bind)(using Context): Tree = tree def transformAlternative(tree: Alternative)(using Context): Tree = tree @@ -394,6 +399,16 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { val expansion = transformTree(tree.expansion, start)(using inlineContext(tree.call)) goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), start) } + case tree: Quote => + inContext(prepQuote(tree, start)(using outerCtx)) { + val body = transformTree(tree.body, start)(using quoteContext) + goQuote(cpy.Quote(tree)(body), start) + } + case tree: Splice => + inContext(prepSplice(tree, start)(using outerCtx)) { + val expr = transformTree(tree.expr, start)(using spliceContext) + goSplice(cpy.Splice(tree)(expr), start) + } case tree: Return => inContext(prepReturn(tree, start)(using outerCtx)) { val expr = transformTree(tree.expr, start) @@ -546,6 +561,10 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { private val nxSeqLiteralTransPhase = init("transformSeqLiteral") private val nxInlinedPrepPhase = init("prepareForInlined") private val nxInlinedTransPhase = init("transformInlined") + private val nxQuotePrepPhase = init("prepareForQuote") + private val nxQuoteTransPhase = init("transformQuote") + private val nxSplicePrepPhase = init("prepareForPrep") + private val nxSpliceTransPhase = init("transformSplice") private val nxTypeTreePrepPhase = init("prepareForTypeTree") private val nxTypeTreeTransPhase = init("transformTypeTree") private val nxBindPrepPhase = init("prepareForBind") @@ -893,6 +912,36 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { } } + def prepQuote(tree: Quote, start: Int)(using Context): Context = { + val phase = nxQuotePrepPhase(start) + if (phase == null) ctx + else prepQuote(tree, phase.idxInGroup + 1)(using phase.prepareForQuote(tree)) + } + + def goQuote(tree: Quote, start: Int)(using Context): Tree = { + val phase = nxQuoteTransPhase(start) + if (phase == null) tree + else phase.transformQuote(tree) match { + case tree1: Quote => goQuote(tree1, phase.idxInGroup + 1) + case tree1 => transformNode(tree1, phase.idxInGroup + 1) + } + } + + def prepSplice(tree: Splice, start: Int)(using Context): Context = { + val phase = nxSplicePrepPhase(start) + if (phase == null) ctx + else prepSplice(tree, phase.idxInGroup + 1)(using phase.prepareForSplice(tree)) + } + + def goSplice(tree: Splice, start: Int)(using Context): Tree = { + val phase = nxSpliceTransPhase(start) + if (phase == null) tree + else phase.transformSplice(tree) match { + case tree1: Splice => goSplice(tree1, phase.idxInGroup + 1) + case tree1 => transformNode(tree1, phase.idxInGroup + 1) + } + } + def prepTypeTree(tree: TypeTree, start: Int)(using Context): Context = { val phase = nxTypeTreePrepPhase(start) if (phase == null) ctx diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 62174c806f09..0c3a94e7d9cd 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -83,9 +83,10 @@ class PickleQuotes extends MacroTransform { override def checkPostCondition(tree: Tree)(using Context): Unit = tree match - case tree: RefTree if !Inlines.inInlineMethod => - assert(!tree.symbol.isQuote) - assert(!tree.symbol.isExprSplice) + case tree: Quote => + 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") @@ -97,21 +98,10 @@ class PickleQuotes extends MacroTransform { protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match - case Apply(Select(Apply(TypeApply(fn, List(tpt)), List(code)),nme.apply), List(quotes)) - if fn.symbol == defn.QuotedRuntime_exprQuote => - val (contents, codeWithHoles) = makeHoles(code) - val sourceRef = Inlines.inlineCallTrace(ctx.owner, tree.sourcePos) - val codeWithHoles2 = Inlined(sourceRef, Nil, codeWithHoles) - val pickled = PickleQuotes(quotes, codeWithHoles2, contents, tpt.tpe, false) + case Apply(Select(quote: Quote, nme.apply), List(quotes)) => + val (contents, quote1) = makeHoles(quote) + val pickled = PickleQuotes.pickle(quote1, quotes, contents) transform(pickled) // pickle quotes that are in the contents - case Apply(TypeApply(_, List(tpt)), List(quotes)) if tree.symbol == defn.QuotedTypeModule_of => - tpt match - case Select(t, _) if tpt.symbol == defn.QuotedType_splice => - // `Type.of[t.Underlying](quotes)` --> `t` - ref(t.symbol)(using ctx.withSource(tpt.source)).withSpan(tpt.span) - case _ => - val (contents, tptWithHoles) = makeHoles(tpt) - PickleQuotes(quotes, tptWithHoles, contents, tpt.tpe, true) case tree: DefDef if !tree.rhs.isEmpty && tree.symbol.isInlineMethod => // Shrink size of the tree. The methods have already been inlined. // TODO move to FirstTransform to trigger even without quotes @@ -120,8 +110,7 @@ class PickleQuotes extends MacroTransform { super.transform(tree) } - private def makeHoles(tree: tpd.Tree)(using Context): (List[Tree], tpd.Tree) = - + private def makeHoles(quote: tpd.Quote)(using Context): (List[Tree], tpd.Quote) = class HoleContentExtractor extends Transformer: private val contents = List.newBuilder[Tree] override def transform(tree: tpd.Tree)(using Context): tpd.Tree = @@ -192,10 +181,13 @@ class PickleQuotes extends MacroTransform { end HoleContentExtractor val holeMaker = new HoleContentExtractor - val newTree = holeMaker.transform(tree) - (holeMaker.getContents(), newTree) - + 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) + (holeMaker.getContents(), quote1) end makeHoles } @@ -206,7 +198,10 @@ object PickleQuotes { val name: String = "pickleQuotes" val description: String = "turn quoted trees into explicit run-time data structures" - def apply(quotes: Tree, body: Tree, contents: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { + def pickle(quote: Quote, quotes: Tree, contents: List[Tree])(using Context) = { + val body = quote.body + val bodyType = quote.bodyType + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ object reflect extends ReifiedReflect { val quotesTree = quotes @@ -260,7 +255,7 @@ object PickleQuotes { */ def liftedValue(lit: Literal, lifter: Symbol) = val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(quotes) + ref(lifter).appliedToType(bodyType).select(nme.apply).appliedTo(lit).appliedTo(quotes) def pickleAsValue(lit: Literal) = { // TODO should all constants be pickled as Literals? @@ -338,18 +333,18 @@ object PickleQuotes { case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) ) - val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass - val quotedType = quoteClass.typeRef.appliedTo(originalTp) + val quoteClass = if quote.isTypeQuote then defn.QuotedTypeClass else defn.QuotedExprClass + val quotedType = quoteClass.typeRef.appliedTo(bodyType) val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) val unpickleMeth = - if isType then defn.QuoteUnpickler_unpickleTypeV2 + if quote.isTypeQuote then defn.QuoteUnpickler_unpickleTypeV2 else defn.QuoteUnpickler_unpickleExprV2 val unpickleArgs = - if isType then List(pickledQuoteStrings, types) + if quote.isTypeQuote then List(pickledQuoteStrings, types) else List(pickledQuoteStrings, types, termHoles) quotes .asInstance(defn.QuoteUnpicklerClass.typeRef) - .select(unpickleMeth).appliedToType(originalTp) + .select(unpickleMeth).appliedToType(bodyType) .appliedToArgs(unpickleArgs).withSpan(body.span) } @@ -376,7 +371,7 @@ object PickleQuotes { case Inlined(_, Nil, e) => getLiteral(e) case _ => None - if (isType) then + if body.isType then if contents.isEmpty && body.symbol.isPrimitiveValueClass then taggedType() else pickleAsTasty() else diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 7f3e47c14732..d74392f201ba 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -342,7 +342,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val patterns1 = transform(patterns) cpy.UnApply(tree)(transform(fun), transform(implicits), patterns1) case tree: TypeApply => - if tree.symbol.isQuote then + if tree.symbol == defn.QuotedTypeModule_of then ctx.compilationUnit.needsStaging = true if tree.symbol.is(Inline) then ctx.compilationUnit.needsInlining = true @@ -485,6 +485,9 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase ) case Block(_, Closure(_, _, tpt)) if ExpandSAMs.needsWrapperClass(tpt.tpe) => superAcc.withInvalidCurrentClass(super.transform(tree)) + case _: Quote => + ctx.compilationUnit.needsStaging = true + super.transform(tree) case tree => super.transform(tree) } diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala index b2059195b8e4..bd0c063cd21a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -75,8 +75,8 @@ trait ReifiedReflect: .select(defn.Quotes_reflect_TypeRepr_of) .appliedToType(tpe) .appliedTo( - ref(defn.QuotedTypeModule_of) - .appliedToType(tpe) + tpd.Quote(TypeTree(tpe)) + .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 bc4119ad0cff..8f45fd94d205 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 Quoted(quotedTree) => quotedTree + case Quote(quotedTree) => 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 Quoted(_) => // ok + case Quote(_) => // ok case _ => type Env = Set[Symbol] @@ -155,15 +155,15 @@ object Splicer { case Block(Nil, expr) => checkIfValidArgument(expr) case Typed(expr, _) => checkIfValidArgument(expr) - case Apply(Select(Apply(fn, quoted :: Nil), nme.apply), _) if fn.symbol == defn.QuotedRuntime_exprQuote => + case Apply(Select(Quote(body), nme.apply), _) => val noSpliceChecker = new TreeTraverser { def traverse(tree: Tree)(using Context): Unit = tree match - case Spliced(_) => + case Splice(_) => report.error("Quoted argument of macros may not have splices", tree.srcPos) case _ => traverseChildren(tree) } - noSpliceChecker.traverse(quoted) + noSpliceChecker.traverse(body) case Apply(TypeApply(fn, List(quoted)), _)if fn.symbol == defn.QuotedTypeModule_of => // OK @@ -203,7 +203,7 @@ object Splicer { case Typed(expr, _) => checkIfValidStaticCall(expr) - case Apply(Select(Apply(fn, quoted :: Nil), nme.apply), _) if fn.symbol == defn.QuotedRuntime_exprQuote => + case Apply(Select(Quote(quoted), nme.apply), _) => // OK, canceled and warning emitted case Call(fn, args) @@ -240,15 +240,15 @@ 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(Apply(TypeApply(fn, _), quoted :: Nil), nme.apply), _) if fn.symbol == defn.QuotedRuntime_exprQuote => - val quoted1 = quoted match { - case quoted: Ident if quoted.symbol.isAllOf(InlineByNameProxy) => + 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 - quoted.symbol.defTree.asInstanceOf[DefDef].rhs - case Inlined(EmptyTree, _, quoted) => quoted - case _ => quoted + expr.symbol.defTree.asInstanceOf[DefDef].rhs + case Inlined(EmptyTree, _, body1) => body1 + case _ => body } - new ExprImpl(Inlined(EmptyTree, Nil, QuoteUtils.changeOwnerOfTree(quoted1, ctx.owner)).withSpan(quoted1.span), SpliceScope.getCurrent) + new ExprImpl(Inlined(EmptyTree, Nil, QuoteUtils.changeOwnerOfTree(body1, ctx.owner)).withSpan(body1.span), SpliceScope.getCurrent) // Interpret level -1 `Type.of[T]` case Apply(TypeApply(fn, quoted :: Nil), _) if fn.symbol == defn.QuotedTypeModule_of => diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index e959f5d1451d..4b288349fc33 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -21,7 +21,6 @@ import dotty.tools.dotc.core.Names._ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.quoted._ import dotty.tools.dotc.config.ScalaRelease.* -import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.staging.QuoteTypeTags @@ -87,10 +86,7 @@ class Splicing extends MacroTransform: override def transform(tree: tpd.Tree)(using Context): tpd.Tree = assert(level == 0) tree match - case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) - if fn.symbol == defn.QuotedRuntime_exprQuote => - QuoteTransformer().transform(tree) - case TypeApply(_, _) if tree.symbol == defn.QuotedTypeModule_of => + case Apply(Select(_: Quote, nme.apply), _) => QuoteTransformer().transform(tree) case tree: DefDef if tree.symbol.is(Inline) => // Quotes in inlined methods are only pickled after they are inlined. @@ -113,17 +109,13 @@ class Splicing extends MacroTransform: override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match - case Apply(fn, List(splicedCode)) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => - if level > 1 then - val splicedCode1 = super.transform(splicedCode)(using spliceContext) - cpy.Apply(tree)(fn, List(splicedCode1)) - else - val holeIdx = numHoles - numHoles += 1 - val splicer = SpliceTransformer(ctx.owner, quotedDefs.contains) - val newSplicedCode1 = splicer.transformSplice(splicedCode, tree.tpe, holeIdx)(using spliceContext) - val newSplicedCode2 = Level0QuoteTransformer.transform(newSplicedCode1)(using spliceContext) - newSplicedCode2 + case tree: Splice if level == 1 => + val holeIdx = numHoles + numHoles += 1 + val splicer = SpliceTransformer(ctx.owner, quotedDefs.contains) + 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 @@ -136,9 +128,6 @@ class Splicing extends MacroTransform: typeHoles.put(qual, hole) hole cpy.TypeDef(tree)(rhs = hole) - case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) - if fn.symbol == defn.QuotedRuntime_exprQuote => - super.transform(tree)(using quoteContext) case _: Template => for sym <- tree.symbol.owner.info.decls do quotedDefs += sym @@ -237,32 +226,18 @@ class Splicing extends MacroTransform: case tree @ Assign(lhs: RefTree, rhs) => if isCaptured(lhs.symbol) then transformSplicedAssign(tree) else super.transform(tree) - case Apply(fn, args) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => - val newArgs = args.mapConserve(arg => transform(arg)(using spliceContext)) - cpy.Apply(tree)(fn, newArgs) - case Apply(sel @ Select(app @ Apply(fn, args),nme.apply), quotesArgs) - if fn.symbol == defn.QuotedRuntime_exprQuote => - args match - case List(tree: RefTree) if isCaptured(tree.symbol) => - capturedTerm(tree) - case _ => - val newArgs = withCurrentQuote(quotesArgs.head) { - if level > 1 then args.mapConserve(arg => transform(arg)(using quoteContext)) - else args.mapConserve(arg => transformLevel0QuoteContent(arg)(using quoteContext)) - } - cpy.Apply(tree)(cpy.Select(sel)(cpy.Apply(app)(fn, newArgs), nme.apply), quotesArgs) - case Apply(TypeApply(typeof, List(tpt)), List(quotes)) - if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) => - val newContent = capturedPartTypes(tpt) - newContent match - case block: Block => - inContext(ctx.withSource(tree.source)) { - Apply(TypeApply(typeof, List(newContent)), List(quotes)).withSpan(tree.span) - } - case _ => - ref(capturedType(newContent))(using ctx.withSource(tree.source)).withSpan(tree.span) case CapturedApplication(fn, argss) => transformCapturedApplication(tree, fn, argss) + 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 _ => super.transform(tree) @@ -413,18 +388,10 @@ class Splicing extends MacroTransform: body(using ctx.withOwner(meth)).changeOwner(ctx.owner, meth) } }) - ref(defn.QuotedRuntime_exprNestedSplice) - .appliedToType(tpe) - .appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it - .appliedTo(closure) + Splice(closure, tpe) private def quoted(expr: Tree)(using Context): Tree = - val tpe = expr.tpe.widenTermRefExpr - ref(defn.QuotedRuntime_exprQuote) - .appliedToType(tpe) - .appliedTo(expr) - .select(nme.apply) - .appliedTo(quotes.nn) + tpd.Quote(expr).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 4fb5f5910440..581fd2753504 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -10,7 +10,6 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.util.SrcPos import dotty.tools.dotc.transform.SymUtils._ -import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.staging.CrossStageSafety import dotty.tools.dotc.staging.HealType @@ -58,6 +57,13 @@ class Staging extends MacroTransform { case _ => } + tree match { + case tree: RefTree => + assert(level != 0 || tree.symbol != defn.QuotedTypeModule_of, + "scala.quoted.Type.of at level 0 should have been replaced with Quote AST in staging phase") + case _ => + } + tree.tpe match { case tpe @ TypeRef(prefix, _) if tpe.typeSymbol.isTypeSplice => // Type splices must have a know term ref, usually to an implicit argument diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index b945f5820523..feb841ba5c6c 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -315,14 +315,6 @@ object SymUtils: def reachableRawTypeRef(using Context) = self.reachableTypeRef.appliedTo(self.typeParams.map(_ => TypeBounds.emptyPolyKind)) - /** Is symbol a quote operation? */ - def isQuote(using Context): Boolean = - self == defn.QuotedRuntime_exprQuote || self == defn.QuotedTypeModule_of - - /** Is symbol a term splice operation? */ - def isExprSplice(using Context): Boolean = - self == defn.QuotedRuntime_exprSplice || self == defn.QuotedRuntime_exprNestedSplice - /** Is symbol a type splice operation? */ def isTypeSplice(using Context): Boolean = self == defn.QuotedType_splice diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 3e05310d7249..2a2c15f95774 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.Quoted(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 8473bd168bc5..3a5ea05726a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -15,7 +15,6 @@ import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.inlines.PrepareInlineable -import dotty.tools.dotc.staging.QuoteContext.* import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.transform.SymUtils._ import dotty.tools.dotc.typer.Implicits._ @@ -37,21 +36,21 @@ trait QuotesAndSplices { */ def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = { record("typedQuote") - tree.quoted match { - case untpd.Splice(innerExpr) if tree.isTerm && !ctx.mode.is(Mode.Pattern) => + tree.body match { + case _: untpd.Splice if tree.isTerm && !ctx.mode.is(Mode.Pattern) => report.warning("Canceled splice directly inside a quote. '{ ${ XYZ } } is equivalent to XYZ.", tree.srcPos) case _ => } - val qctx = inferImplicitArg(defn.QuotesClass.typeRef, tree.span) + val quotes = inferImplicitArg(defn.QuotesClass.typeRef, tree.span) - if qctx.tpe.isInstanceOf[SearchFailureType] then - report.error(missingArgMsg(qctx, defn.QuotesClass.typeRef, ""), ctx.source.atSpan(tree.span)) - else if !qctx.tpe.isStable then - report.error(em"Quotes require stable Quotes, but found non stable $qctx", qctx.srcPos) + if quotes.tpe.isInstanceOf[SearchFailureType] then + report.error(missingArgMsg(quotes, defn.QuotesClass.typeRef, ""), ctx.source.atSpan(tree.span)) + else if !quotes.tpe.isStable then + report.error(em"Quotes require stable Quotes, but found non stable $quotes", quotes.srcPos) if ctx.mode.is(Mode.Pattern) then - typedQuotePattern(tree, pt, qctx).withSpan(tree.span) - else if tree.quoted.isType then + typedQuotePattern(tree, pt, quotes).withSpan(tree.span) + else if tree.isTypeQuote then val msg = em"""Quoted types `'[..]` can only be used in patterns. | |Hint: To get a scala.quoted.Type[T] use scala.quoted.Type.of[T] instead. @@ -59,8 +58,11 @@ trait QuotesAndSplices { report.error(msg, tree.srcPos) EmptyTree else - val exprQuoteTree = untpd.Apply(untpd.ref(defn.QuotedRuntime_exprQuote.termRef), tree.quoted) - makeInlineable(typedApply(exprQuoteTree, pt)(using pushQuotes(qctx)).select(nme.apply).appliedTo(qctx).withSpan(tree.span)) + // 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) + makeInlineable(quotedExpr.select(nme.apply).appliedTo(quotes).withSpan(tree.span)) } private def makeInlineable(tree: Tree)(using Context): Tree = @@ -75,6 +77,7 @@ trait QuotesAndSplices { tree.expr match { case untpd.Quote(innerExpr) if innerExpr.isTerm => report.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.srcPos) + return typed(innerExpr, pt) case _ => } if (ctx.mode.is(Mode.QuotedPattern)) @@ -85,7 +88,7 @@ trait QuotesAndSplices { using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(spliceOwner(ctx))) val baseType = pat.tpe.baseType(defn.QuotedExprClass) val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType - ref(defn.QuotedRuntime_exprSplice).appliedToType(argType).appliedTo(pat) + Splice(pat, argType).withSpan(tree.span) } else { report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.srcPos) @@ -102,17 +105,20 @@ trait QuotesAndSplices { markAsMacro(ctx) } - val (outerQctx, ctx1) = popQuotes() - + // TODO typecheck directly (without `exprSplice`) val internalSplice = - outerQctx match - case Some(qctxRef) => untpd.Apply(untpd.Apply(untpd.ref(defn.QuotedRuntime_exprNestedSplice.termRef), qctxRef), tree.expr) - case _ => untpd.Apply(untpd.ref(defn.QuotedRuntime_exprSplice.termRef), tree.expr) - - typedApply(internalSplice, pt)(using ctx1).withSpan(tree.span) + untpd.Apply(untpd.ref(defn.QuotedRuntime_exprSplice.termRef), tree.expr) + typedApply(internalSplice, pt)(using spliceContext).withSpan(tree.span) match + case tree @ Apply(TypeApply(_, tpt :: Nil), spliced :: Nil) if tree.symbol == defn.QuotedRuntime_exprSplice => + cpy.Splice(tree)(spliced) + case tree => tree } } + def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = + val tpt = typedType(tree.tpt) + assignType(tree, tpt) + /** Types a splice applied to some arguments `$f(arg1, ..., argn)` in a quote pattern. * * The tree is desugared into `$f.apply(arg1, ..., argn)` where the expression `$f` @@ -123,10 +129,11 @@ trait QuotesAndSplices { def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = { assert(ctx.mode.is(Mode.QuotedPattern)) val untpd.Apply(splice: untpd.Splice, args) = tree: @unchecked + def isInBraces: Boolean = splice.span.end != splice.expr.span.end if !isFullyDefined(pt, ForceDegree.flipBottom) then report.error(em"Type must be fully defined.", splice.srcPos) tree.withType(UnspecifiedErrorType) - else if splice.isInBraces then // ${x}(...) match an application + else if isInBraces then // ${x}(...) match an application val typedArgs = args.map(arg => typedExpr(arg)) val argTypes = typedArgs.map(_.tpe.widenTermRefExpr) val splice1 = typedSplice(splice, defn.FunctionOf(argTypes, pt)) @@ -164,10 +171,6 @@ trait QuotesAndSplices { using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx))) pat.select(tpnme.Underlying) - def typedHole(tree: untpd.Hole, 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) @@ -225,12 +228,10 @@ trait QuotesAndSplices { val freshTypeBindingsBuff = new mutable.ListBuffer[Tree] val typePatBuf = new mutable.ListBuffer[Tree] override def transform(tree: Tree)(using Context) = tree match { - case Typed(Apply(fn, pat :: Nil), tpt) if fn.symbol.isExprSplice && !tpt.tpe.derivesFrom(defn.RepeatedParamClass) => - val tpt1 = transform(tpt) // Transform type bindings - val exprTpt = AppliedTypeTree(TypeTree(defn.QuotedExprClass.typeRef), tpt1 :: Nil) - val newSplice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tpt1.tpe).appliedTo(Typed(pat, exprTpt)) - transform(newSplice) - case Apply(TypeApply(fn, targs), Apply(sp, pat :: Nil) :: args :: Nil) if fn.symbol == defn.QuotedRuntimePatterns_patternHigherOrderHole => + case Typed(splice: Splice, tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) => + transform(tpt) // Collect type bindings + transform(splice) + case Apply(TypeApply(fn, targs), Splice(pat) :: args :: Nil) if fn.symbol == defn.QuotedRuntimePatterns_patternHigherOrderHole => args match // TODO support these patterns. Possibly using scala.quoted.util.Var case SeqLiteral(args, _) => for arg <- args; if arg.symbol.is(Mutable) do @@ -242,7 +243,7 @@ trait QuotesAndSplices { val pat1 = if (patType eq patType1) pat else pat.withType(patType1) patBuf += pat1 } - case Apply(fn, pat :: Nil) if fn.symbol.isExprSplice => + case Splice(pat) => try ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span) finally { val patType = pat.tpe.widen @@ -381,13 +382,13 @@ trait QuotesAndSplices { * ) => ... * ``` */ - private def typedQuotePattern(tree: untpd.Quote, pt: Type, qctx: Tree)(using Context): Tree = { - if tree.quoted.isTerm && !pt.derivesFrom(defn.QuotedExprClass) then + private def typedQuotePattern(tree: untpd.Quote, pt: Type, quotes: Tree)(using Context): Tree = { + val quoted = tree.body + if quoted.isTerm && !pt.derivesFrom(defn.QuotedExprClass) then report.error("Quote pattern can only match scrutinees of type scala.quoted.Expr", tree.srcPos) - else if tree.quoted.isType && !pt.derivesFrom(defn.QuotedTypeClass) then + else if quoted.isType && !pt.derivesFrom(defn.QuotedTypeClass) then report.error("Quote pattern can only match scrutinees of type scala.quoted.Type", tree.srcPos) - val quoted = tree.quoted val exprPt = pt.baseType(if quoted.isType then defn.QuotedTypeClass else defn.QuotedExprClass) val quotedPt = exprPt.argInfos.headOption match { case Some(argPt: ValueType) => argPt // excludes TypeBounds @@ -439,13 +440,13 @@ trait QuotesAndSplices { if splices.isEmpty then ref(defn.EmptyTupleModule.termRef) else typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType) - val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass + val quoteClass = if (quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass val quotedPattern = - if (tree.quoted.isTerm) ref(defn.QuotedRuntime_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx) - else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape).appliedTo(qctx) + if (quoted.isTerm) tpd.Quote(shape).select(nme.apply).appliedTo(quotes) + else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape).appliedTo(quotes) - val matchModule = if tree.quoted.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch - val unapplyFun = qctx.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(nme.unapply) + val matchModule = if quoted.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch + val unapplyFun = quotes.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(nme.unapply) UnApply( fun = unapplyFun.appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil), diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index c64f541fd811..f8cfd4ca3ae0 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -12,6 +12,7 @@ import ast.{tpd, untpd} import scala.util.control.NonFatal import util.Spans.Span import Nullables._ +import staging.StagingLevel.* /** A version of Typer that keeps all symbols defined and referenced in a * previously typed tree. @@ -94,6 +95,21 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking override def typedUnApply(tree: untpd.Apply, selType: Type)(using Context): Tree = typedApply(tree, selType) + 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) + + override def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = + assertTyped(tree) + val exprType = // Expr[T] + defn.QuotedExprClass.typeRef.appliedTo(tree.typeOpt) + val quoteType = // Quotes ?=> Expr[T] + defn.FunctionType(1, isContextual = true) + .appliedTo(defn.QuotesClass.typeRef, exprType) + val expr1 = typed(tree.expr, quoteType)(using spliceContext) + untpd.cpy.Splice(tree)(expr1).withType(tree.typeOpt) + override def localDummy(cls: ClassSymbol, impl: untpd.Template)(using Context): Symbol = impl.symbol override def retrieveSym(tree: untpd.Tree)(using Context): Symbol = tree.symbol diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 1949304ca287..245bcdac70af 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -609,11 +609,13 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end extension end NamedArgMethods - type Apply = tpd.Apply + type Apply = tpd.Apply | tpd.Quote | tpd.Splice object ApplyTypeTest extends TypeTest[Tree, Apply]: def unapply(x: Tree): Option[Apply & x.type] = x match case x: (tpd.Apply & x.type) => Some(x) + case x: (tpd.Quote & x.type) => Some(x) // TODO expose Quote AST in Quotes + case x: (tpd.Splice & x.type) => Some(x) // TODO expose Splice AST in Quotes case _ => None end ApplyTypeTest @@ -630,8 +632,23 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler given ApplyMethods: ApplyMethods with extension (self: Apply) - def fun: Term = self.fun - def args: List[Term] = self.args + def fun: Term = self match + case self: tpd.Apply => self.fun + case self: tpd.Quote => // TODO expose Quote AST in Quotes + import dotty.tools.dotc.ast.tpd.TreeOps + tpd.ref(dotc.core.Symbols.defn.QuotedRuntime_exprQuote) + .appliedToType(self.bodyType) + .withSpan(self.span) + case self: tpd.Splice => // TODO expose Splice AST in Quotes + import dotty.tools.dotc.ast.tpd.TreeOps + tpd.ref(dotc.core.Symbols.defn.QuotedRuntime_exprSplice) + .appliedToType(self.tpe) + .withSpan(self.span) + + def args: List[Term] = self match + case self: tpd.Apply => self.args + case self: tpd.Quote => List(self.body) // TODO expose Quote AST in Quotes + case self: tpd.Splice => List(self.expr) // TODO expose Splice AST in Quotes end extension end ApplyMethods diff --git a/compiler/test-resources/repl-macros/i5551 b/compiler/test-resources/repl-macros/i5551 index d71251e9a824..984551438b51 100644 --- a/compiler/test-resources/repl-macros/i5551 +++ b/compiler/test-resources/repl-macros/i5551 @@ -1,7 +1,7 @@ scala> import scala.quoted._ scala> def assertImpl(expr: Expr[Boolean])(using q: Quotes) = '{ if !($expr) then throw new AssertionError("failed assertion")} def assertImpl - (expr: quoted.Expr[Boolean])(using q: quoted.Quotes): quoted.Expr[Unit] + (expr: quoted.Expr[Boolean])(using q: quoted.Quotes): scala.quoted.Expr[Unit] scala> inline def assert(expr: => Boolean): Unit = ${ assertImpl('{expr}) } def assert(expr: => Boolean): Unit diff --git a/compiler/test-resources/repl/i10355 b/compiler/test-resources/repl/i10355 index bfe3af835c87..294b9d7f1101 100644 --- a/compiler/test-resources/repl/i10355 +++ b/compiler/test-resources/repl/i10355 @@ -1,5 +1,7 @@ scala> import scala.quoted._ scala> def foo(expr: Expr[Any])(using Quotes) = expr match { case '{ $x: t } => '{ $x: Any } } -def foo(expr: quoted.Expr[Any])(using x$2: quoted.Quotes): quoted.Expr[Any] +def foo + (expr: quoted.Expr[Any])(using x$2: quoted.Quotes): scala.quoted.Expr[Any] scala> def bar(expr: Expr[Any])(using Quotes) = expr match { case '{ $x: t } => '{ val a: t = ??? ; ???} } -def bar(expr: quoted.Expr[Any])(using x$2: quoted.Quotes): quoted.Expr[Nothing] +def bar + (expr: quoted.Expr[Any])(using x$2: quoted.Quotes): scala.quoted.Expr[Nothing] diff --git a/staging/test-resources/repl-staging/i6007 b/staging/test-resources/repl-staging/i6007 index be9d5c0f92d6..0d6fbd0cffb1 100644 --- a/staging/test-resources/repl-staging/i6007 +++ b/staging/test-resources/repl-staging/i6007 @@ -3,7 +3,7 @@ scala> import quoted.staging.{Compiler => StagingCompiler, _} scala> implicit def compiler: StagingCompiler = StagingCompiler.make(getClass.getClassLoader) def compiler: quoted.staging.Compiler scala> def v(using Quotes) = '{ (if true then Some(1) else None).map(v => v+1) } -def v(using x$1: quoted.Quotes): quoted.Expr[Option[Int]] +def v(using x$1: quoted.Quotes): scala.quoted.Expr[Option[Int]] scala> scala.quoted.staging.withQuotes(v.show) val res0: String = (if (true) scala.Some.apply[scala.Int](1) else scala.None).map[scala.Int](((v: scala.Int) => v.+(1))) scala> scala.quoted.staging.run(v) diff --git a/tests/neg-macros/i10127-a.scala b/tests/neg-macros/i10127-a.scala index 3e23cf10bd30..2da4d0924870 100644 --- a/tests/neg-macros/i10127-a.scala +++ b/tests/neg-macros/i10127-a.scala @@ -1,7 +1,7 @@ import scala.quoted.* object T { - def impl[A](using t: Type[A])(using Quotes): Expr[Unit] = { + def impl[A](t: Type[A])(using Quotes): Expr[Unit] = { Expr.summon[t.Underlying] // error '{} } diff --git a/tests/neg-macros/i10127-b.scala b/tests/neg-macros/i10127-b.scala index 2e87e92efa63..13992bf95362 100644 --- a/tests/neg-macros/i10127-b.scala +++ b/tests/neg-macros/i10127-b.scala @@ -4,7 +4,7 @@ case class T(x: Type[_ <: Any]) object T { def impl[A](t: T)(using ctx: Quotes): Expr[Unit] = { - Expr.summon[t.x.Underlying] // error // error + Expr.summon[t.x.Underlying] // error '{} } } \ No newline at end of file diff --git a/tests/neg-macros/i6991.check b/tests/neg-macros/i6991.check new file mode 100644 index 000000000000..57d611a09053 --- /dev/null +++ b/tests/neg-macros/i6991.check @@ -0,0 +1,10 @@ +-- [E050] Type Error: tests/neg-macros/i6991.scala:11:14 --------------------------------------------------------------- +11 | case '{($x: Foo)($bar: String)} => '{"Hello World"} // error + | ^^^^^^^ + | expression does not take parameters + | + | longer explanation available when compiling with `-explain` +-- [E008] Not Found Error: tests/neg-macros/i6991.scala:12:23 ---------------------------------------------------------- +12 | case '{($x: Foo).apply($bar: String)} => '{"Hello World"} // error + | ^^^^^^^^^^^^^^^ + | value apply is not a member of macros.Foo diff --git a/tests/neg-macros/i6991.scala b/tests/neg-macros/i6991.scala new file mode 100644 index 000000000000..c6838261ed7a --- /dev/null +++ b/tests/neg-macros/i6991.scala @@ -0,0 +1,16 @@ +import scala.quoted._ + +object macros { + inline def mcr(x: => Any): Any = ${mcrImpl('x)} + + class Foo // { def apply(str: String) = "100" } + class Bar { def apply(str: String) = "100" } + + def mcrImpl(body: Expr[Any])(using ctx: Quotes): Expr[Any] = { + body match { + case '{($x: Foo)($bar: String)} => '{"Hello World"} // error + case '{($x: Foo).apply($bar: String)} => '{"Hello World"} // error + case '{($x: Bar)($bar: String)} => '{"Hello World"} + } + } +} diff --git a/tests/pos-macros/i10127-a.scala b/tests/pos-macros/i10127-a.scala new file mode 100644 index 000000000000..3b9efc2a829d --- /dev/null +++ b/tests/pos-macros/i10127-a.scala @@ -0,0 +1,8 @@ +import scala.quoted.* + +object T { + def impl[A](using t: Type[A])(using Quotes): Expr[Unit] = { + Expr.summon[t.Underlying] + '{} + } +} \ No newline at end of file diff --git a/tests/run-staging/multi-staging.check b/tests/run-staging/multi-staging.check index 5d12306ba4ef..76adcfec3034 100644 --- a/tests/run-staging/multi-staging.check +++ b/tests/run-staging/multi-staging.check @@ -1,5 +1,5 @@ stage1 code: ((q1: scala.quoted.Quotes) ?=> { val x1: scala.Int = 2 - scala.quoted.runtime.Expr.quote[scala.Int](1.+(scala.quoted.runtime.Expr.nestedSplice[scala.Int](q1)(((evidence$5: scala.quoted.Quotes) ?=> scala.quoted.Expr.apply[scala.Int](x1)(scala.quoted.ToExpr.IntToExpr[scala.Int])(evidence$5))))).apply(using q1) + scala.quoted.runtime.Expr.quote[scala.Int](1.+(scala.quoted.runtime.Expr.splice[scala.Int](((evidence$5: scala.quoted.Quotes) ?=> scala.quoted.Expr.apply[scala.Int](x1)(scala.quoted.ToExpr.IntToExpr[scala.Int])(evidence$5))))).apply(using q1) }) 3 diff --git a/tests/run-staging/quote-nested-2.check b/tests/run-staging/quote-nested-2.check index b0af638da1a8..7db9edb0424e 100644 --- a/tests/run-staging/quote-nested-2.check +++ b/tests/run-staging/quote-nested-2.check @@ -1,4 +1,4 @@ ((q: scala.quoted.Quotes) ?=> { val a: scala.quoted.Expr[scala.Int] = scala.quoted.runtime.Expr.quote[scala.Int](4).apply(using q) - ((evidence$2: scala.quoted.Quotes) ?=> a).asInstanceOf[scala.ContextFunction1[scala.quoted.Quotes, scala.quoted.Expr[scala.Int]]].apply(using q) + ((evidence$2: scala.quoted.Quotes) ?=> a).apply(using q) }) diff --git a/tests/run-staging/quote-nested-5.check b/tests/run-staging/quote-nested-5.check index f29acb3b347a..53600d16a8da 100644 --- a/tests/run-staging/quote-nested-5.check +++ b/tests/run-staging/quote-nested-5.check @@ -1,4 +1,4 @@ ((q: scala.quoted.Quotes) ?=> { val a: scala.quoted.Expr[scala.Int] = scala.quoted.runtime.Expr.quote[scala.Int](4).apply(using q) - ((q2: scala.quoted.Quotes) ?=> ((evidence$3: scala.quoted.Quotes) ?=> a).asInstanceOf[scala.ContextFunction1[scala.quoted.Quotes, scala.quoted.Expr[scala.Int]]].apply(using q2)).apply(using q) -}) + ((q2: scala.quoted.Quotes) ?=> ((evidence$2: scala.quoted.Quotes) ?=> a).apply(using q2)) +}.apply(using q))