Skip to content

Commit c43a302

Browse files
committed
Add SplicePattern AST to parse and type quote pattern splices
1 parent 5a9b616 commit c43a302

File tree

8 files changed

+98
-75
lines changed

8 files changed

+98
-75
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,9 @@ object desugar {
338338
def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(using Context): untpd.Tree = {
339339
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
340340
// Add the expected type as an ascription
341-
case _: untpd.Splice =>
341+
case _: untpd.SplicePattern =>
342342
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
343-
case Typed(expr: untpd.Splice, tpt) =>
343+
case Typed(expr: untpd.SplicePattern, tpt) =>
344344
cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span))
345345

346346
// Propagate down the expected type to the leafs of the expression
@@ -1989,7 +1989,7 @@ object desugar {
19891989
case Quote(body) =>
19901990
new UntypedTreeTraverser {
19911991
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
1992-
case Splice(expr) => collect(expr)
1992+
case SplicePattern(body, _) => collect(body)
19931993
case _ => traverseChildren(tree)
19941994
}
19951995
}.traverse(body)

compiler/src/dotty/tools/dotc/ast/Trees.scala

+25
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,22 @@ object Trees {
732732
type ThisTree[+T <: Untyped] = Splice[T]
733733
}
734734

735+
/** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern.
736+
*
737+
* Parser will only create `${ pattern }` and `$ident`, hence they will not have args.
738+
* While typing, the `$ident(args*)` the args are identified and desugared into a `SplicePattern`
739+
* containing them.
740+
*
741+
* SplicePattern are removed after typing the pattern and are not present in TASTy.
742+
*
743+
* @param body The tree that was spliced
744+
* @param args The arguments of the splice (the HOAS arguments)
745+
*/
746+
case class SplicePattern[+T <: Untyped] private[ast] (body: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
747+
extends TermTree[T] {
748+
type ThisTree[+T <: Untyped] = SplicePattern[T]
749+
}
750+
735751
/** A type tree that represents an existing or inferred type */
736752
case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile)
737753
extends DenotingTree[T] with TypTree[T] {
@@ -1143,6 +1159,7 @@ object Trees {
11431159
type Inlined = Trees.Inlined[T]
11441160
type Quote = Trees.Quote[T]
11451161
type Splice = Trees.Splice[T]
1162+
type SplicePattern = Trees.SplicePattern[T]
11461163
type TypeTree = Trees.TypeTree[T]
11471164
type InferredTypeTree = Trees.InferredTypeTree[T]
11481165
type SingletonTypeTree = Trees.SingletonTypeTree[T]
@@ -1321,6 +1338,10 @@ object Trees {
13211338
case tree: Splice if (expr eq tree.expr) => tree
13221339
case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree)))
13231340
}
1341+
def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match {
1342+
case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree
1343+
case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree)))
1344+
}
13241345
def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match {
13251346
case tree: SingletonTypeTree if (ref eq tree.ref) => tree
13261347
case _ => finalize(tree, untpd.SingletonTypeTree(ref)(sourceFile(tree)))
@@ -1562,6 +1583,8 @@ object Trees {
15621583
cpy.Quote(tree)(transform(body)(using quoteContext))
15631584
case tree @ Splice(expr) =>
15641585
cpy.Splice(tree)(transform(expr)(using spliceContext))
1586+
case tree @ SplicePattern(body, args) =>
1587+
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
15651588
case tree @ Hole(isTerm, idx, args, content, tpt) =>
15661589
cpy.Hole(tree)(isTerm, idx, transform(args), transform(content), transform(tpt))
15671590
case _ =>
@@ -1707,6 +1730,8 @@ object Trees {
17071730
this(x, body)(using quoteContext)
17081731
case Splice(expr) =>
17091732
this(x, expr)(using spliceContext)
1733+
case SplicePattern(body, args) =>
1734+
this(this(x, body)(using spliceContext), args)
17101735
case Hole(_, _, args, content, tpt) =>
17111736
this(this(this(x, args), content), tpt)
17121737
case _ =>

compiler/src/dotty/tools/dotc/ast/untpd.scala

+1
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
399399
def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion)
400400
def Quote(body: Tree)(implicit src: SourceFile): Quote = new Quote(body)
401401
def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr)
402+
def SplicePattern(body: Tree, args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, args)
402403
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
403404
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()
404405
def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref)

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -1745,10 +1745,10 @@ object Parsers {
17451745
def splice(isType: Boolean): Tree =
17461746
val start = in.offset
17471747
atSpan(in.offset) {
1748+
val inPattern = (staged & StageKind.QuotedPattern) != 0
17481749
val expr =
17491750
if (in.name.length == 1) {
17501751
in.nextToken()
1751-
val inPattern = (staged & StageKind.QuotedPattern) != 0
17521752
withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock())
17531753
}
17541754
else atSpan(in.offset + 1) {
@@ -1764,6 +1764,8 @@ object Parsers {
17641764
else "To use a given Type[T] in a quote just write T directly"
17651765
syntaxError(em"$msg\n\nHint: $hint", Span(start, in.lastOffset))
17661766
Ident(nme.ERROR.toTypeName)
1767+
else if inPattern then
1768+
SplicePattern(expr, Nil)
17671769
else
17681770
Splice(expr)
17691771
}

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+6
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
724724
case Splice(expr) =>
725725
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
726726
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")
727+
case SplicePattern(pattern, args) =>
728+
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
729+
keywordStr("$") ~ spliceTypeText ~ {
730+
if args.isEmpty then keywordStr("{") ~ inPattern(toText(pattern)) ~ keywordStr("}")
731+
else toText(pattern.symbol.name) ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
732+
}
727733
case Hole(isTerm, idx, args, content, tpt) =>
728734
val (prefix, postfix) = if isTerm then ("{{{", "}}}") else ("[[[", "]]]")
729735
val argsText = toTextGlobal(args, ", ")

compiler/src/dotty/tools/dotc/typer/Applications.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,7 @@ trait Applications extends Compatibility {
10971097
}
10981098
else {
10991099
val app = tree.fun match
1100-
case _: untpd.Splice if ctx.mode.is(Mode.QuotedPattern) => typedAppliedSplice(tree, pt)
1100+
case _: untpd.SplicePattern => typedAppliedSplice(tree, pt)
11011101
case _ => realApply
11021102
app match {
11031103
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

+58-70
Original file line numberDiff line numberDiff line change
@@ -74,44 +74,57 @@ trait QuotesAndSplices {
7474
def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = {
7575
record("typedSplice")
7676
checkSpliceOutsideQuote(tree)
77+
assert(!ctx.mode.is(Mode.QuotedPattern))
7778
tree.expr match {
7879
case untpd.Quote(innerExpr) if innerExpr.isTerm =>
7980
report.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.srcPos)
8081
return typed(innerExpr, pt)
8182
case _ =>
8283
}
83-
if (ctx.mode.is(Mode.QuotedPattern))
84-
if (isFullyDefined(pt, ForceDegree.flipBottom)) {
85-
def spliceOwner(ctx: Context): Symbol =
86-
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
87-
val pat = typedPattern(tree.expr, defn.QuotedExprClass.typeRef.appliedTo(pt))(
88-
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(spliceOwner(ctx)))
89-
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
90-
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
91-
Splice(pat, argType).withSpan(tree.span)
92-
}
93-
else {
94-
report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.srcPos)
95-
tree.withType(UnspecifiedErrorType)
84+
if (level == 0) {
85+
// Mark the first inline method from the context as a macro
86+
def markAsMacro(c: Context): Unit =
87+
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
88+
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
89+
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
90+
else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro
91+
markAsMacro(ctx)
92+
}
93+
94+
// TODO typecheck directly (without `exprSplice`)
95+
val internalSplice =
96+
untpd.Apply(untpd.ref(defn.QuotedRuntime_exprSplice.termRef), tree.expr)
97+
typedApply(internalSplice, pt)(using spliceContext).withSpan(tree.span) match
98+
case tree @ Apply(TypeApply(_, tpt :: Nil), spliced :: Nil) if tree.symbol == defn.QuotedRuntime_exprSplice =>
99+
cpy.Splice(tree)(spliced)
100+
case tree => tree
101+
}
102+
103+
def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = {
104+
record("typedSplicePattern")
105+
if (isFullyDefined(pt, ForceDegree.flipBottom)) {
106+
def spliceOwner(ctx: Context): Symbol =
107+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
108+
val typedArgs = tree.args.map {
109+
case arg: untpd.Ident =>
110+
typedExpr(arg)
111+
case arg =>
112+
report.error("Open pattern expected an identifier", arg.srcPos)
113+
EmptyTree
96114
}
115+
for arg <- typedArgs if arg.symbol.is(Mutable) do // TODO support these patterns. Possibly using scala.quoted.util.Var
116+
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
117+
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
118+
val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt)
119+
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(
120+
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(spliceOwner(ctx)))
121+
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
122+
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
123+
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
124+
}
97125
else {
98-
if (level == 0) {
99-
// Mark the first inline method from the context as a macro
100-
def markAsMacro(c: Context): Unit =
101-
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
102-
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
103-
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
104-
else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro
105-
markAsMacro(ctx)
106-
}
107-
108-
// TODO typecheck directly (without `exprSplice`)
109-
val internalSplice =
110-
untpd.Apply(untpd.ref(defn.QuotedRuntime_exprSplice.termRef), tree.expr)
111-
typedApply(internalSplice, pt)(using spliceContext).withSpan(tree.span) match
112-
case tree @ Apply(TypeApply(_, tpt :: Nil), spliced :: Nil) if tree.symbol == defn.QuotedRuntime_exprSplice =>
113-
cpy.Splice(tree)(spliced)
114-
case tree => tree
126+
report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.body.srcPos)
127+
tree.withType(UnspecifiedErrorType)
115128
}
116129
}
117130

@@ -128,29 +141,17 @@ trait QuotesAndSplices {
128141
*/
129142
def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = {
130143
assert(ctx.mode.is(Mode.QuotedPattern))
131-
val untpd.Apply(splice: untpd.Splice, args) = tree: @unchecked
132-
def isInBraces: Boolean = splice.span.end != splice.expr.span.end
133-
if !isFullyDefined(pt, ForceDegree.flipBottom) then
134-
report.error(em"Type must be fully defined.", splice.srcPos)
135-
tree.withType(UnspecifiedErrorType)
136-
else if isInBraces then // ${x}(...) match an application
144+
val untpd.Apply(splice: untpd.SplicePattern, args) = tree: @unchecked
145+
def isInBraces: Boolean = splice.span.end != splice.body.span.end
146+
if isInBraces then // ${x}(...) match an application
137147
val typedArgs = args.map(arg => typedExpr(arg))
138148
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
139-
val splice1 = typedSplice(splice, defn.FunctionOf(argTypes, pt))
140-
Apply(splice1.select(nme.apply), typedArgs).withType(pt).withSpan(tree.span)
149+
val splice1 = typedSplicePattern(splice, defn.FunctionOf(argTypes, pt))
150+
untpd.cpy.Apply(tree)(splice1.select(nme.apply), typedArgs).withType(pt)
141151
else // $x(...) higher-order quasipattern
142-
val typedArgs = args.map {
143-
case arg: untpd.Ident =>
144-
typedExpr(arg)
145-
case arg =>
146-
report.error("Open pattern expected an identifier", arg.srcPos)
147-
EmptyTree
148-
}
149152
if args.isEmpty then
150-
report.error("Missing arguments for open pattern", tree.srcPos)
151-
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
152-
val typedPat = typedSplice(splice, defn.FunctionOf(argTypes, pt))
153-
ref(defn.QuotedRuntimePatterns_patternHigherOrderHole).appliedToType(pt).appliedTo(typedPat, SeqLiteral(typedArgs, TypeTree(defn.AnyType)))
153+
report.error("Missing arguments for open pattern", tree.srcPos)
154+
typedSplicePattern(untpd.cpy.SplicePattern(tree)(splice.body, args), pt)
154155
}
155156

156157
/** Type a pattern variable name `t` in quote pattern as `${given t$giveni: Type[t @ _]}`.
@@ -228,29 +229,16 @@ trait QuotesAndSplices {
228229
val freshTypeBindingsBuff = new mutable.ListBuffer[Tree]
229230
val typePatBuf = new mutable.ListBuffer[Tree]
230231
override def transform(tree: Tree)(using Context) = tree match {
231-
case Typed(splice: Splice, tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
232+
case Typed(splice @ SplicePattern(pat, Nil), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
232233
transform(tpt) // Collect type bindings
233234
transform(splice)
234-
case Apply(TypeApply(fn, targs), Splice(pat) :: args :: Nil) if fn.symbol == defn.QuotedRuntimePatterns_patternHigherOrderHole =>
235-
args match // TODO support these patterns. Possibly using scala.quoted.util.Var
236-
case SeqLiteral(args, _) =>
237-
for arg <- args; if arg.symbol.is(Mutable) do
238-
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
239-
try ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToTypeTrees(targs).appliedTo(args).withSpan(tree.span)
240-
finally {
241-
val patType = pat.tpe.widen
242-
val patType1 = patType.translateFromRepeated(toArray = false)
243-
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
244-
patBuf += pat1
245-
}
246-
case Splice(pat) =>
247-
try ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span)
248-
finally {
249-
val patType = pat.tpe.widen
250-
val patType1 = patType.translateFromRepeated(toArray = false)
251-
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
252-
patBuf += pat1
253-
}
235+
case SplicePattern(pat, args) =>
236+
val patType = pat.tpe.widen
237+
val patType1 = patType.translateFromRepeated(toArray = false)
238+
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
239+
patBuf += pat1
240+
if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span)
241+
else ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))).withSpan(tree.span)
254242
case Select(pat: Bind, _) if tree.symbol.isTypeSplice =>
255243
val sym = tree.tpe.dealias.typeSymbol
256244
if sym.exists then

compiler/src/dotty/tools/dotc/typer/Typer.scala

+1
Original file line numberDiff line numberDiff line change
@@ -3088,6 +3088,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30883088
case untpd.EmptyTree => tpd.EmptyTree
30893089
case tree: untpd.Quote => typedQuote(tree, pt)
30903090
case tree: untpd.Splice => typedSplice(tree, pt)
3091+
case tree: untpd.SplicePattern => typedSplicePattern(tree, pt)
30913092
case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here
30923093
case tree: untpd.Hole => typedHole(tree, pt)
30933094
case _ => typedUnadapted(desugar(tree, pt), pt, locked)

0 commit comments

Comments
 (0)