diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 8a080972fb31..f0580c29e762 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -338,9 +338,9 @@ object desugar { def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(using Context): untpd.Tree = { def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match { // Add the expected type as an ascription - case _: untpd.Splice => + case _: untpd.SplicePattern => untpd.Typed(tree, expectedTpt).withSpan(tree.span) - case Typed(expr: untpd.Splice, tpt) => + case Typed(expr: untpd.SplicePattern, tpt) => cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span)) // Propagate down the expected type to the leafs of the expression @@ -1979,7 +1979,7 @@ object desugar { case Quote(body, _) => new UntypedTreeTraverser { def traverse(tree: untpd.Tree)(using Context): Unit = tree match { - case Splice(expr) => collect(expr) + case SplicePattern(body, _) => collect(body) case _ => traverseChildren(tree) } }.traverse(body) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index f3d7369d8d5d..54c15b9909fa 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -737,6 +737,22 @@ object Trees { type ThisTree[+T <: Untyped] = Splice[T] } + /** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern. + * + * Parser will only create `${ pattern }` and `$ident`, hence they will not have args. + * While typing, the `$ident(args*)` the args are identified and desugared into a `SplicePattern` + * containing them. + * + * SplicePattern are removed after typing the pattern and are not present in TASTy. + * + * @param body The tree that was spliced + * @param args The arguments of the splice (the HOAS arguments) + */ + case class SplicePattern[+T <: Untyped] private[ast] (body: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) + extends TermTree[T] { + type ThisTree[+T <: Untyped] = SplicePattern[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] { @@ -1147,6 +1163,7 @@ object Trees { type Inlined = Trees.Inlined[T] type Quote = Trees.Quote[T] type Splice = Trees.Splice[T] + type SplicePattern = Trees.SplicePattern[T] type TypeTree = Trees.TypeTree[T] type InferredTypeTree = Trees.InferredTypeTree[T] type SingletonTypeTree = Trees.SingletonTypeTree[T] @@ -1325,6 +1342,10 @@ object Trees { case tree: Splice if (expr eq tree.expr) => tree case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree))) } + def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match { + case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree + case _ => finalize(tree, untpd.SplicePattern(body, args)(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))) @@ -1566,6 +1587,8 @@ object Trees { cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags)) case tree @ Splice(expr) => cpy.Splice(tree)(transform(expr)(using spliceContext)) + case tree @ SplicePattern(body, args) => + cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args)) case tree @ Hole(isTerm, idx, args, content) => cpy.Hole(tree)(isTerm, idx, transform(args), transform(content)) case _ => @@ -1711,6 +1734,8 @@ object Trees { this(this(x, body)(using quoteContext), tags) case Splice(expr) => this(x, expr)(using spliceContext) + case SplicePattern(body, args) => + this(this(x, body)(using spliceContext), args) case Hole(_, _, args, content) => this(this(x, args), content) case _ => diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 91299be537e8..e3488034fef8 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -399,6 +399,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion) 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 SplicePattern(body: Tree, args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, args) 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) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7ea113cb7bc7..7a29ac3f7a38 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1750,10 +1750,10 @@ object Parsers { def splice(isType: Boolean): Tree = val start = in.offset atSpan(in.offset) { + val inPattern = (staged & StageKind.QuotedPattern) != 0 val expr = if (in.name.length == 1) { in.nextToken() - val inPattern = (staged & StageKind.QuotedPattern) != 0 withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock()) } else atSpan(in.offset + 1) { @@ -1769,6 +1769,8 @@ object Parsers { else "To use a given Type[T] in a quote just write T directly" syntaxError(em"$msg\n\nHint: $hint", Span(start, in.lastOffset)) Ident(nme.ERROR.toTypeName) + else if inPattern then + SplicePattern(expr, Nil) else Splice(expr) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 0b539db283ed..51aaa0932e5e 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -735,6 +735,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case Splice(expr) => val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}") + case SplicePattern(pattern, args) => + val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists) + keywordStr("$") ~ spliceTypeText ~ { + if args.isEmpty then keywordStr("{") ~ inPattern(toText(pattern)) ~ keywordStr("}") + else toText(pattern.symbol.name) ~ "(" ~ toTextGlobal(args, ", ") ~ ")" + } case Hole(isTerm, idx, args, content) => val (prefix, postfix) = if isTerm then ("{{{", "}}}") else ("[[[", "]]]") val argsText = toTextGlobal(args, ", ") diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 79d6501ccb2d..fbed4b77d3fe 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1097,7 +1097,7 @@ trait Applications extends Compatibility { } else { val app = tree.fun match - case _: untpd.Splice if ctx.mode.is(Mode.QuotedPattern) => typedAppliedSplice(tree, pt) + case _: untpd.SplicePattern => typedAppliedSplice(tree, pt) case _ => realApply app match { case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index ea2b380dc0ed..070449e3ee96 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -17,6 +17,7 @@ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.inlines.PrepareInlineable import dotty.tools.dotc.staging.StagingLevel.* import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.typer.ErrorReporting.errorTree import dotty.tools.dotc.typer.Implicits._ import dotty.tools.dotc.typer.Inferencing._ import dotty.tools.dotc.util.Spans._ @@ -74,45 +75,55 @@ trait QuotesAndSplices { def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = { record("typedSplice") checkSpliceOutsideQuote(tree) + assert(!ctx.mode.is(Mode.QuotedPattern)) tree.expr match { 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 _ => } - if (ctx.mode.is(Mode.QuotedPattern)) - if (isFullyDefined(pt, ForceDegree.flipBottom)) { - def spliceOwner(ctx: Context): Symbol = - if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner - val pat = typedPattern(tree.expr, defn.QuotedExprClass.typeRef.appliedTo(pt))( - 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 - 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) - tree.withType(UnspecifiedErrorType) - } - else { - if (level == 0) { - // Mark the first inline method from the context as a macro - def markAsMacro(c: Context): Unit = - if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isInlineMethod) c.owner.setFlag(Macro) - else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) - else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro - markAsMacro(ctx) - } - - // TODO typecheck directly (without `exprSplice`) - val internalSplice = - 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 + if (level == 0) { + // Mark the first inline method from the context as a macro + def markAsMacro(c: Context): Unit = + if (c.owner eq c.outer.owner) markAsMacro(c.outer) + else if (c.owner.isInlineMethod) c.owner.setFlag(Macro) + else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) + else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro + markAsMacro(ctx) } + + // TODO typecheck directly (without `exprSplice`) + val internalSplice = + 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 typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = { + record("typedSplicePattern") + if isFullyDefined(pt, ForceDegree.flipBottom) then + def patternOuterContext(ctx: Context): Context = + if (ctx.mode.is(Mode.QuotedPattern)) patternOuterContext(ctx.outer) else ctx + val typedArgs = tree.args.map { + case arg: untpd.Ident => + typedExpr(arg) + case arg => + report.error("Open pattern expected an identifier", arg.srcPos) + EmptyTree + } + for arg <- typedArgs if arg.symbol.is(Mutable) do // TODO support these patterns. Possibly using scala.quoted.util.Var + report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos) + val argTypes = typedArgs.map(_.tpe.widenTermRefExpr) + val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt) + val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))( + using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(patternOuterContext(ctx).owner)) + val baseType = pat.tpe.baseType(defn.QuotedExprClass) + val argType = if baseType.exists then baseType.argTypesHi.head else defn.NothingType + untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt) + else + errorTree(tree, em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.body.srcPos) } def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = @@ -127,29 +138,17 @@ 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 isInBraces then // ${x}(...) match an application + val untpd.Apply(splice: untpd.SplicePattern, args) = tree: @unchecked + def isInBraces: Boolean = splice.span.end != splice.body.span.end + 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)) - Apply(splice1.select(nme.apply), typedArgs).withType(pt).withSpan(tree.span) + val splice1 = typedSplicePattern(splice, defn.FunctionOf(argTypes, pt)) + untpd.cpy.Apply(tree)(splice1.select(nme.apply), typedArgs).withType(pt) else // $x(...) higher-order quasipattern - val typedArgs = args.map { - case arg: untpd.Ident => - typedExpr(arg) - case arg => - report.error("Open pattern expected an identifier", arg.srcPos) - EmptyTree - } if args.isEmpty then - report.error("Missing arguments for open pattern", tree.srcPos) - val argTypes = typedArgs.map(_.tpe.widenTermRefExpr) - val typedPat = typedSplice(splice, defn.FunctionOf(argTypes, pt)) - ref(defn.QuotedRuntimePatterns_patternHigherOrderHole).appliedToType(pt).appliedTo(typedPat, SeqLiteral(typedArgs, TypeTree(defn.AnyType))) + report.error("Missing arguments for open pattern", tree.srcPos) + typedSplicePattern(untpd.cpy.SplicePattern(tree)(splice.body, args), pt) } /** Type a pattern variable name `t` in quote pattern as `${given t$giveni: Type[t @ _]}`. @@ -227,29 +226,16 @@ 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(splice: Splice, tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) => + case Typed(splice @ SplicePattern(pat, Nil), 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 - report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos) - try ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToTypeTrees(targs).appliedTo(args).withSpan(tree.span) - finally { - val patType = pat.tpe.widen - val patType1 = patType.translateFromRepeated(toArray = false) - val pat1 = if (patType eq patType1) pat else pat.withType(patType1) - patBuf += pat1 - } - case Splice(pat) => - try ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span) - finally { - val patType = pat.tpe.widen - val patType1 = patType.translateFromRepeated(toArray = false) - val pat1 = if (patType eq patType1) pat else pat.withType(patType1) - patBuf += pat1 - } + case SplicePattern(pat, args) => + val patType = pat.tpe.widen + val patType1 = patType.translateFromRepeated(toArray = false) + val pat1 = if (patType eq patType1) pat else pat.withType(patType1) + patBuf += pat1 + if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span) + else ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))).withSpan(tree.span) case Select(pat: Bind, _) if tree.symbol.isTypeSplice => val sym = tree.tpe.dealias.typeSymbol if sym.exists then diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cfc9e968ef41..9a9e194c9e0b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3096,6 +3096,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case untpd.EmptyTree => tpd.EmptyTree case tree: untpd.Quote => typedQuote(tree, pt) case tree: untpd.Splice => typedSplice(tree, pt) + case tree: untpd.SplicePattern => typedSplicePattern(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here case tree: untpd.Hole => typedHole(tree, pt) case _ => typedUnadapted(desugar(tree, pt), pt, locked)