Skip to content

Commit 4c9cf0a

Browse files
authored
[experimental feature] Support HOAS pattern with type variables for quote pattern matching (#18271)
This PR extends higher-order patterns inside quote patterns to allow type parameters. When this PR is merged, we'll be able to write quote patterns like the following example with an experimental flag `experimental.quotedPatternsWithPolymorphicFunctions`. ```scala def decomposePoly(x: Expr[Any])(using Quotes): Expr[Int] = x match case '{ [A] => (x: List[A]) => $y[A](x) : Int } => '{ $y[Int](List(1, 2, 3)) } case _ => Expr(0) ``` You can see that the higher-order pattern `$y[A](x)` carries an type parameter `A`. It states that this pattern matches a code fragment with occurrences of `A`, and `y` is assigned a polymorphic function type `[A] => List[A] => x`. This PR mainly changes two parts: type checker and quote pattern matcher. Those changes are based on the formalized type system defined in [Nicolas Stucki's thesis](https://github.com/nicolasstucki#thesis), and one can expect the soundness of the implementation. ## Type Dependency If a higher-order pattern carries a value parameter with a type that has type parameters defined in the quoted pattern, those type parameters should also be captured in the higher-order pattern. For example, the following pattern will not be typed. ``` case '{ [A] => (x: List[A]) => $y(x) : Int } => ``` In this case, `x` has the type `List[A]`, which includes a type variable `A` that is defined in the pattern. However, the higher-order pattern `$y(x)` does not have any type parameters. This should be ill-typed. One can always avoid this kind of type errors by adding type parameters, like `$y[A](x)` ## Implementation Restriction Current implementation only allows type parameters that do not have bounds, because sound typing rules for such pattern is not clear yet. ```scala case '{ [A] => (x: List[A]) => $y(x) : Int } => // Allowed case '{ [A <: Int] => (x: List[A]) => $y(x) : Int } => // Disallowed
2 parents af933c4 + 41e2d52 commit 4c9cf0a

38 files changed

+624
-125
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -2218,7 +2218,7 @@ object desugar {
22182218
case Quote(body, _) =>
22192219
new UntypedTreeTraverser {
22202220
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
2221-
case SplicePattern(body, _) => collect(body)
2221+
case SplicePattern(body, _, _) => collect(body)
22222222
case _ => traverseChildren(tree)
22232223
}
22242224
}.traverse(body)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
871871
}
872872
private object quotePatVars extends TreeAccumulator[List[Symbol]] {
873873
def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match {
874-
case SplicePattern(pat, _) => outer.apply(syms, pat)
874+
case SplicePattern(pat, _, _) => outer.apply(syms, pat)
875875
case _ => foldOver(syms, tree)
876876
}
877877
}

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

+9-8
Original file line numberDiff line numberDiff line change
@@ -763,9 +763,10 @@ object Trees {
763763
* `SplicePattern` can only be contained within a `QuotePattern`.
764764
*
765765
* @param body The tree that was spliced
766+
* @param typeargs The type arguments of the splice (the HOAS arguments)
766767
* @param args The arguments of the splice (the HOAS arguments)
767768
*/
768-
case class SplicePattern[+T <: Untyped] private[ast] (body: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
769+
case class SplicePattern[+T <: Untyped] private[ast] (body: Tree[T], typeargs: List[Tree[T]], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
769770
extends TermTree[T] {
770771
type ThisTree[+T <: Untyped] = SplicePattern[T]
771772
}
@@ -1372,9 +1373,9 @@ object Trees {
13721373
case tree: QuotePattern if (bindings eq tree.bindings) && (body eq tree.body) && (quotes eq tree.quotes) => tree
13731374
case _ => finalize(tree, untpd.QuotePattern(bindings, body, quotes)(sourceFile(tree)))
13741375
}
1375-
def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match {
1376-
case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree
1377-
case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree)))
1376+
def SplicePattern(tree: Tree)(body: Tree, typeargs: List[Tree], args: List[Tree])(using Context): SplicePattern = tree match {
1377+
case tree: SplicePattern if (body eq tree.body) && (typeargs eq tree.typeargs) & (args eq tree.args) => tree
1378+
case _ => finalize(tree, untpd.SplicePattern(body, typeargs, args)(sourceFile(tree)))
13781379
}
13791380
def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match {
13801381
case tree: SingletonTypeTree if (ref eq tree.ref) => tree
@@ -1622,8 +1623,8 @@ object Trees {
16221623
cpy.Splice(tree)(transform(expr)(using spliceContext))
16231624
case tree @ QuotePattern(bindings, body, quotes) =>
16241625
cpy.QuotePattern(tree)(transform(bindings), transform(body)(using quoteContext), transform(quotes))
1625-
case tree @ SplicePattern(body, args) =>
1626-
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
1626+
case tree @ SplicePattern(body, targs, args) =>
1627+
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(targs), transform(args))
16271628
case tree @ Hole(isTerm, idx, args, content) =>
16281629
cpy.Hole(tree)(isTerm, idx, transform(args), transform(content))
16291630
case _ =>
@@ -1771,8 +1772,8 @@ object Trees {
17711772
this(x, expr)(using spliceContext)
17721773
case QuotePattern(bindings, body, quotes) =>
17731774
this(this(this(x, bindings), body)(using quoteContext), quotes)
1774-
case SplicePattern(body, args) =>
1775-
this(this(x, body)(using spliceContext), args)
1775+
case SplicePattern(body, typeargs, args) =>
1776+
this(this(this(x, body)(using spliceContext), typeargs), args)
17761777
case Hole(_, _, args, content) =>
17771778
this(this(x, args), content)
17781779
case _ =>

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
181181
def Splice(expr: Tree)(using Context): Splice =
182182
ta.assignType(untpd.Splice(expr), expr)
183183

184-
def SplicePattern(pat: Tree, args: List[Tree], tpe: Type)(using Context): SplicePattern =
185-
untpd.SplicePattern(pat, args).withType(tpe)
184+
def SplicePattern(pat: Tree, targs: List[Tree], args: List[Tree], tpe: Type)(using Context): SplicePattern =
185+
untpd.SplicePattern(pat, targs, args).withType(tpe)
186186

187187
def Hole(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpe: Type)(using Context): Hole =
188188
untpd.Hole(isTerm, idx, args, content).withType(tpe)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
415415
def Quote(body: Tree, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags)
416416
def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr)
417417
def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree)(implicit src: SourceFile): QuotePattern = new QuotePattern(bindings, body, quotes)
418-
def SplicePattern(body: Tree, args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, args)
418+
def SplicePattern(body: Tree, typeargs: List[Tree], args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, typeargs, args)
419419
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
420420
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()
421421
def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref)

compiler/src/dotty/tools/dotc/config/Feature.scala

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ object Feature:
3737
val namedTuples = experimental("namedTuples")
3838
val modularity = experimental("modularity")
3939
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
40+
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
4041

4142
def experimentalAutoEnableFeatures(using Context): List[TermName] =
4243
defn.languageExperimentalFeatures
@@ -130,6 +131,9 @@ object Feature:
130131

131132
def betterMatchTypeExtractorsEnabled(using Context) = enabled(betterMatchTypeExtractors)
132133

134+
def quotedPatternsWithPolymorphicFunctionsEnabled(using Context) =
135+
enabled(quotedPatternsWithPolymorphicFunctions)
136+
133137
/** Is pureFunctions enabled for this compilation unit? */
134138
def pureFunsEnabled(using Context) =
135139
enabledBySetting(pureFunctions)

compiler/src/dotty/tools/dotc/core/Definitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ class Definitions {
903903
@tu lazy val QuotedRuntimePatterns: Symbol = requiredModule("scala.quoted.runtime.Patterns")
904904
@tu lazy val QuotedRuntimePatterns_patternHole: Symbol = QuotedRuntimePatterns.requiredMethod("patternHole")
905905
@tu lazy val QuotedRuntimePatterns_higherOrderHole: Symbol = QuotedRuntimePatterns.requiredMethod("higherOrderHole")
906+
@tu lazy val QuotedRuntimePatterns_higherOrderHoleWithTypes: Symbol = QuotedRuntimePatterns.requiredMethod("higherOrderHoleWithTypes")
906907
@tu lazy val QuotedRuntimePatterns_patternTypeAnnot: ClassSymbol = QuotedRuntimePatterns.requiredClass("patternType")
907908
@tu lazy val QuotedRuntimePatterns_fromAboveAnnot: ClassSymbol = QuotedRuntimePatterns.requiredClass("fromAbove")
908909

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -776,8 +776,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
776776
pickleType(tree.tpe)
777777
bindings.foreach(pickleTree)
778778
}
779-
case SplicePattern(pat, args) =>
780-
val targs = Nil // SplicePattern `targs` will be added with #18271
779+
case SplicePattern(pat, targs, args) =>
781780
writeByte(SPLICEPATTERN)
782781
withLength {
783782
pickleTree(pat)

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -1668,8 +1668,7 @@ class TreeUnpickler(reader: TastyReader,
16681668
val pat = readTree()
16691669
val patType = readType()
16701670
val (targs, args) = until(end)(readTree()).span(_.isType)
1671-
assert(targs.isEmpty, "unexpected type arguments in SPLICEPATTERN") // `targs` will be needed for #18271. Until this fearure is added they should be empty.
1672-
SplicePattern(pat, args, patType)
1671+
SplicePattern(pat, targs, args, patType)
16731672
case HOLE =>
16741673
readHole(end, isTerm = true)
16751674
case _ =>

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1966,7 +1966,7 @@ object Parsers {
19661966
syntaxError(em"$msg\n\nHint: $hint", Span(start, in.lastOffset))
19671967
Ident(nme.ERROR.toTypeName)
19681968
else if inPattern then
1969-
SplicePattern(expr, Nil)
1969+
SplicePattern(expr, Nil, Nil)
19701970
else
19711971
Splice(expr)
19721972
}

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -793,11 +793,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
793793
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
794794
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
795795
keywordStr("'") ~ quotesText ~ open ~ bindingsText ~ toTextGlobal(body) ~ close
796-
case SplicePattern(pattern, args) =>
796+
case SplicePattern(pattern, typeargs, args) =>
797797
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
798798
keywordStr("$") ~ spliceTypeText ~ {
799-
if args.isEmpty then keywordStr("{") ~ inPattern(toText(pattern)) ~ keywordStr("}")
800-
else toText(pattern) ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
799+
if typeargs.isEmpty && args.isEmpty then keywordStr("{") ~ inPattern(toText(pattern)) ~ keywordStr("}")
800+
else if typeargs.isEmpty then toText(pattern) ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
801+
else toText(pattern) ~ "[" ~ toTextGlobal(typeargs, ", ")~ "]" ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
801802
}
802803
case Hole(isTerm, idx, args, content) =>
803804
val (prefix, postfix) = if isTerm then ("{{{", "}}}") else ("[[[", "]]]")

0 commit comments

Comments
 (0)