Skip to content

Commit 0730eae

Browse files
committed
Add SplicePattern AST to parse and type quote pattern splices
1 parent 8a055d6 commit 0730eae

File tree

8 files changed

+100
-75
lines changed

8 files changed

+100
-75
lines changed

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

Lines changed: 3 additions & 3 deletions
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

Lines changed: 25 additions & 0 deletions
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] {
@@ -1144,6 +1160,7 @@ object Trees {
11441160
type Inlined = Trees.Inlined[T]
11451161
type Quote = Trees.Quote[T]
11461162
type Splice = Trees.Splice[T]
1163+
type SplicePattern = Trees.SplicePattern[T]
11471164
type TypeTree = Trees.TypeTree[T]
11481165
type InferredTypeTree = Trees.InferredTypeTree[T]
11491166
type SingletonTypeTree = Trees.SingletonTypeTree[T]
@@ -1322,6 +1339,10 @@ object Trees {
13221339
case tree: Splice if (expr eq tree.expr) => tree
13231340
case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree)))
13241341
}
1342+
def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match {
1343+
case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree
1344+
case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree)))
1345+
}
13251346
def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match {
13261347
case tree: SingletonTypeTree if (ref eq tree.ref) => tree
13271348
case _ => finalize(tree, untpd.SingletonTypeTree(ref)(sourceFile(tree)))
@@ -1563,6 +1584,8 @@ object Trees {
15631584
cpy.Quote(tree)(transform(body)(using quoteContext))
15641585
case tree @ Splice(expr) =>
15651586
cpy.Splice(tree)(transform(expr)(using spliceContext))
1587+
case tree @ SplicePattern(body, args) =>
1588+
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
15661589
case tree @ Hole(_, _, args, content, tpt) =>
15671590
cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt))
15681591
case _ =>
@@ -1708,6 +1731,8 @@ object Trees {
17081731
this(x, body)(using quoteContext)
17091732
case Splice(expr) =>
17101733
this(x, expr)(using spliceContext)
1734+
case SplicePattern(body, args) =>
1735+
this(this(x, body)(using spliceContext), args)
17111736
case Hole(_, _, args, content, tpt) =>
17121737
this(this(this(x, args), content), tpt)
17131738
case _ =>

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 6 additions & 0 deletions
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+
val open = Str(keywordStr("{")).provided(args.isEmpty)
730+
val close = Str(keywordStr("}")).provided(args.isEmpty)
731+
val argsText = ("(" ~ toTextGlobal(args, ", ") ~ ")").provided(args.nonEmpty)
732+
keywordStr("$") ~ spliceTypeText ~ open ~ inPattern(toText(pattern)) ~ close ~ argsText
727733
case Hole(isTermHole, idx, args, content, tpt) =>
728734
val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]")
729735
val argsText = toTextGlobal(args, ", ")

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 60 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -74,44 +74,59 @@ 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 if arg.symbol.is(Mutable) =>
110+
// TODO support these patterns. Possibly using scala.quoted.util.Var
111+
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
112+
EmptyTree
113+
case arg: untpd.Ident =>
114+
typedExpr(arg)
115+
case arg =>
116+
report.error("Open pattern expected an identifier", arg.srcPos)
117+
EmptyTree
96118
}
119+
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
120+
val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt)
121+
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(
122+
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(spliceOwner(ctx)))
123+
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
124+
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
125+
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
126+
}
97127
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
128+
report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.body.srcPos)
129+
tree.withType(UnspecifiedErrorType)
115130
}
116131
}
117132

@@ -128,29 +143,17 @@ trait QuotesAndSplices {
128143
*/
129144
def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = {
130145
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
146+
val untpd.Apply(splice: untpd.SplicePattern, args) = tree: @unchecked
147+
def isInBraces: Boolean = splice.span.end != splice.body.span.end
148+
if isInBraces then // ${x}(...) match an application
137149
val typedArgs = args.map(arg => typedExpr(arg))
138150
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)
151+
val splice1 = typedSplicePattern(splice, defn.FunctionOf(argTypes, pt))
152+
untpd.cpy.Apply(tree)(splice1.select(nme.apply), typedArgs).withType(pt)
141153
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-
}
149154
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)))
155+
report.error("Missing arguments for open pattern", tree.srcPos)
156+
typedSplicePattern(untpd.cpy.SplicePattern(tree)(splice.body, args), pt)
154157
}
155158

156159
/** Type a pattern variable name `t` in quote pattern as `${given t$giveni: Type[t @ _]}`.
@@ -228,29 +231,16 @@ trait QuotesAndSplices {
228231
val freshTypeBindingsBuff = new mutable.ListBuffer[Tree]
229232
val typePatBuf = new mutable.ListBuffer[Tree]
230233
override def transform(tree: Tree)(using Context) = tree match {
231-
case Typed(splice: Splice, tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
234+
case Typed(splice @ SplicePattern(pat, Nil), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
232235
transform(tpt) // Collect type bindings
233236
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-
}
237+
case SplicePattern(pat, args) =>
238+
val patType = pat.tpe.widen
239+
val patType1 = patType.translateFromRepeated(toArray = false)
240+
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
241+
patBuf += pat1
242+
if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span)
243+
else ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))).withSpan(tree.span)
254244
case Select(pat: Bind, _) if tree.symbol.isTypeSplice =>
255245
val sym = tree.tpe.dealias.typeSymbol
256246
if sym.exists then

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

Lines changed: 1 addition & 0 deletions
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)