Skip to content

Commit a626cfa

Browse files
committed
Add SplicePattern AST to parse and type quote pattern splices
1 parent cba5c9a commit a626cfa

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
@@ -1979,7 +1979,7 @@ object desugar {
19791979
case Quote(body, _) =>
19801980
new UntypedTreeTraverser {
19811981
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
1982-
case Splice(expr) => collect(expr)
1982+
case SplicePattern(body, _) => collect(body)
19831983
case _ => traverseChildren(tree)
19841984
}
19851985
}.traverse(body)

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

+25
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,22 @@ object Trees {
737737
type ThisTree[+T <: Untyped] = Splice[T]
738738
}
739739

740+
/** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern.
741+
*
742+
* Parser will only create `${ pattern }` and `$ident`, hence they will not have args.
743+
* While typing, the `$ident(args*)` the args are identified and desugared into a `SplicePattern`
744+
* containing them.
745+
*
746+
* SplicePattern are removed after typing the pattern and are not present in TASTy.
747+
*
748+
* @param body The tree that was spliced
749+
* @param args The arguments of the splice (the HOAS arguments)
750+
*/
751+
case class SplicePattern[+T <: Untyped] private[ast] (body: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
752+
extends TermTree[T] {
753+
type ThisTree[+T <: Untyped] = SplicePattern[T]
754+
}
755+
740756
/** A type tree that represents an existing or inferred type */
741757
case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile)
742758
extends DenotingTree[T] with TypTree[T] {
@@ -1147,6 +1163,7 @@ object Trees {
11471163
type Inlined = Trees.Inlined[T]
11481164
type Quote = Trees.Quote[T]
11491165
type Splice = Trees.Splice[T]
1166+
type SplicePattern = Trees.SplicePattern[T]
11501167
type TypeTree = Trees.TypeTree[T]
11511168
type InferredTypeTree = Trees.InferredTypeTree[T]
11521169
type SingletonTypeTree = Trees.SingletonTypeTree[T]
@@ -1325,6 +1342,10 @@ object Trees {
13251342
case tree: Splice if (expr eq tree.expr) => tree
13261343
case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree)))
13271344
}
1345+
def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match {
1346+
case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree
1347+
case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree)))
1348+
}
13281349
def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match {
13291350
case tree: SingletonTypeTree if (ref eq tree.ref) => tree
13301351
case _ => finalize(tree, untpd.SingletonTypeTree(ref)(sourceFile(tree)))
@@ -1566,6 +1587,8 @@ object Trees {
15661587
cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags))
15671588
case tree @ Splice(expr) =>
15681589
cpy.Splice(tree)(transform(expr)(using spliceContext))
1590+
case tree @ SplicePattern(body, args) =>
1591+
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
15691592
case tree @ Hole(isTerm, idx, args, content) =>
15701593
cpy.Hole(tree)(isTerm, idx, transform(args), transform(content))
15711594
case _ =>
@@ -1711,6 +1734,8 @@ object Trees {
17111734
this(this(x, body)(using quoteContext), tags)
17121735
case Splice(expr) =>
17131736
this(x, expr)(using spliceContext)
1737+
case SplicePattern(body, args) =>
1738+
this(this(x, body)(using spliceContext), args)
17141739
case Hole(_, _, args, content) =>
17151740
this(this(x, args), content)
17161741
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, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags)
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
@@ -1750,10 +1750,10 @@ object Parsers {
17501750
def splice(isType: Boolean): Tree =
17511751
val start = in.offset
17521752
atSpan(in.offset) {
1753+
val inPattern = (staged & StageKind.QuotedPattern) != 0
17531754
val expr =
17541755
if (in.name.length == 1) {
17551756
in.nextToken()
1756-
val inPattern = (staged & StageKind.QuotedPattern) != 0
17571757
withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock())
17581758
}
17591759
else atSpan(in.offset + 1) {
@@ -1769,6 +1769,8 @@ object Parsers {
17691769
else "To use a given Type[T] in a quote just write T directly"
17701770
syntaxError(em"$msg\n\nHint: $hint", Span(start, in.lastOffset))
17711771
Ident(nme.ERROR.toTypeName)
1772+
else if inPattern then
1773+
SplicePattern(expr, Nil)
17721774
else
17731775
Splice(expr)
17741776
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
735735
case Splice(expr) =>
736736
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
737737
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")
738+
case SplicePattern(pattern, args) =>
739+
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
740+
keywordStr("$") ~ spliceTypeText ~ {
741+
if args.isEmpty then keywordStr("{") ~ inPattern(toText(pattern)) ~ keywordStr("}")
742+
else toText(pattern.symbol.name) ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
743+
}
738744
case Hole(isTerm, idx, args, content) =>
739745
val (prefix, postfix) = if isTerm then ("{{{", "}}}") else ("[[[", "]]]")
740746
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, Nil) 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

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

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

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

+1
Original file line numberDiff line numberDiff line change
@@ -3096,6 +3096,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30963096
case untpd.EmptyTree => tpd.EmptyTree
30973097
case tree: untpd.Quote => typedQuote(tree, pt)
30983098
case tree: untpd.Splice => typedSplice(tree, pt)
3099+
case tree: untpd.SplicePattern => typedSplicePattern(tree, pt)
30993100
case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here
31003101
case tree: untpd.Hole => typedHole(tree, pt)
31013102
case _ => typedUnadapted(desugar(tree, pt), pt, locked)

0 commit comments

Comments
 (0)