Skip to content

Commit 1e471fa

Browse files
Merge pull request #6334 from dotty-staging/add-quoted-patterns-spec
Add spec for quoted patterns
2 parents 58a36f2 + ef107bf commit 1e471fa

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
layout: doc-page
3+
title: "Pattern Matching on Quoted Code"
4+
---
5+
6+
7+
## Overview
8+
9+
Any top-level quote `'{ ... }` in a pattern position will become a quoted pattern. Inside quoted pattern parts of the code can be spliced with `$` which extracts that part of the code.
10+
Splices can be of two forms:
11+
* A splice `${ ... : Expr[T] }` that can be placed in any expression position.
12+
* A splice `${ ... : Bind[T] }` that can be placed on names of `val`s, `var`s or `def`s
13+
14+
```scala
15+
def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match {
16+
case '{ val $a: Int = $x; (${Bind(`a`)}: Int) + 1 } => '{ $x + 1 } // TODO needs fix for #6328, `a` is currently not in scope while typing
17+
}
18+
```
19+
In the example above we have `$a` which provides a `Bind[Int]`, `$x` which provides an `Expr[Int]` and `${Bind(`a`)}` which probides an `Expr[Int]` that is pattern matched against `Bind(`a`)` to check that it is a reference to `a`.
20+
21+
Quoted patterns are transformed during typer to a call of `scala.internal.quoted.Matcher.unapply` which splits the quoted code into the patterns and a reifiable quote that will be used as witnesses at runtime.
22+
23+
```scala
24+
def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match {
25+
case scala.internal.quoted.Matcher.unapply[Tuple3[Bind[Int], Expr[Int], Expr[Int]]](Tuple3(a, x, Bind(`a`), y))('{ @patternBindHole val a: Int = patternHole[Int]; patternHole[Int] + 1 }) =>
26+
}
27+
```
28+
29+
30+
## Runtime semantics
31+
32+
At runtime to a `quoted.Expr` can be matched to another using `scala.internal.quoted.Matcher.unapply`.
33+
34+
```scala
35+
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup]
36+
```
37+
38+
The `scrutineeExpr` is a normal quoted expression while `patternExpr` may contain holes representing splices.
39+
The result of pattern matching is `None` if the expressions are not equivalent, otherwise it returns `Some` (some tuple) containing the contents of the matched holes.
40+
41+
Let's define some abstractions on the possible results of this pattern matching using the alias `Matching`:
42+
```scala
43+
type Matching = Option[Tuple]
44+
type Env
45+
46+
def notMatched = None
47+
def matched = Some(()) // aka Some(Tuple0())
48+
def matched[T](x: T) = Some(Tuple1(x))
49+
def (x: Matching) && (y: Matching) = if (x == None || y == None) None else Some(x.get ++ y.get)
50+
def fold[T](m: Mattching*) given Env: Matching = m.fold(matched)(_ && _)
51+
52+
// `a =#= b` stands for `a` matches `b`
53+
def (scrutinee: Tree) =#= pattern: Tree) given Env: Matching // described by cases in the tables below
54+
55+
def envWith(equiv: (Symbol, Symbol)*) given Env: Env // Adds to the current environment the fact that s1 from the scrutinee is equivalent to s2 in the pattern
56+
57+
def equivalent(s1: Symbol, s2: Symbol) given Env: Env
58+
```
59+
60+
The implementation of `=#=`
61+
62+
| Tree | Pattern | Returns |
63+
| :-----------------------: | :-------------------------: | :---------- |
64+
| Term `a` | `patternHole[X]` | `match(quoted.Expr[X]('a))` if type of `a` is a subtype of `X`
65+
| `val a: A` | `@patternBindHole val x: X` | `match(quoted.Bind[X](a.sym)) && '{val a: A} =#= '{val x: X}`
66+
| Literal `a` | Literal `x` | `matched` if value of `a` is equal to the value of `x`
67+
| `a` | `x` | `matched` if `equivalent(a.sym, x.sym)`
68+
| `a.b` | `x.y` | `'a =#= 'x` if `equivalent(b.sym, y.sym)`
69+
| `a: A` | `x: X` | `'a =#= 'x && '[A] =#= '[X]`
70+
| `fa(.. ai ..)` | `fx(.. xi ..)` | `'fa =#= 'fx && fold(.. 'ai =#= 'xi) ..)`
71+
| `fa[.. Ai ..]` | `fx[.. Xi ..]` | `'fa =#= 'fx && fold(.. '[Ai] =#= '[Xi] ..)`
72+
| `{.. ai ..}` | `{.. xi ..}` | `fold(.. 'ai =#= 'xi ..)`
73+
| `if (a) b else c` | `if (x) y else z` | `'a =#= 'x && 'b =#= 'y && 'c =#= 'z`
74+
| `while (a) b` | `while (x) y` | `'a =#= 'x && 'b =#= 'y`
75+
| Assignment `a = b` | Assignment `x = y` | `'b =#= 'y` if `'a =#= 'x.nonEmpty`
76+
| Named argument<br>`n = a` | Named argument<br>`m = x` | `'a =#= 'x`
77+
| `Seq(.. ai ..): _*` | `Seq(.. xi ..): _*` | `fold(.. 'ai =#= 'xi ..)`
78+
| `new A` | `new X` | `'[A] =#= '[X]`
79+
| `this` | `this` | `matched` if both refer to the same symbol
80+
| `a.super[B]` | `x.super[Y]` | `'a =#= 'x` if `B` equals `Y`
81+
| `val a: A = b`<br>`lazy val a: A = b`<br>`var a: A = b` | `val x: X = y`<br>`lazy val x: X = y`<br>`var x: X = y` | `'[A] =#= '[X] && 'b =#= 'y given envWith(a.sym -> b.sym)`
82+
| `def a[..Ai..](.. bij: Bij ..): A = c` | `def x[..Xi..](.. yij: Yij ..): X = z` | `fold(..'[Ai] =#= '[Xi]..) && fold(.. 'bij =#= 'yij && '[Bij] =#= '[Yij] ..) && '[A] =#= '[X] && 'c =#= 'z given envWith(a.sym -> b.sym, .. bij.sym -> yij.sym ..)`
83+
| `(.. ai: Ai ..) => b` | `(.. xi: Xi ..) => y` | `fold(.. 'ai =#= 'xi && '[Ai] =#= '[Xi] ..) && 'b =#= 'y given envWith(.. ai.sym -> xi.sym ..)`
84+
| `a match { .. bi .. }` | `x match { .. yi .. }` | `'a =#= 'x && fold(.. 'bi =#= 'yi ..)`
85+
| `try a catch { .. bi .. } finally ci` | `try x catch { .. yi .. } finally z` | `'a =#= 'x && fold(.. 'bi =#= 'yi ..) && 'c =#= 'z`
86+
| | |
87+
| `case a if b => c` | `case x if y => z` | `'a =#= 'x && 'b =#= 'y && c =#= z`
88+
| | |
89+
| Inferred `A` | Inferred `X` | `matched` if `A <:< X`
90+
| `A[.. Bi ..]` | `X[.. Yi ..]` | `matched` if `('[A] && '[X] && fold(.. '[Bi] =#= '[Yi] ..)).nonEmpty`
91+
| `A @annot` | `X` | `'[A] =#= '[X]`
92+
| `A` | `X @annot` | `'[A] && '[X]`
93+
| | | `notMatched`
94+
95+
96+
| Pattern inside the quote | Pattern | Returns |
97+
| :-------------------------: |:--------------------------: | :------------- |
98+
| Value `a` | Value `x` | `'a =#= 'x`
99+
| `a: A` | `x: X` | `'[A] && '[X]`
100+
| `a @ b` | `x @ y` | `'b =#= 'y given envWith(a.sym -> b.sym)`
101+
| Unapply `a(..bi..)(..ci..)` | Unapply `x(..yi..)(..zi..)` | `'a =#= 'x && fold(.. 'bi =#= 'yi ..) && fold(.. 'ci =#= 'zi ..)`
102+
| `.. | ai | ..` | `.. | xi | ..` | `fold(.. 'ai =#= 'xi ..)`
103+
| `_` | `_` | `matched`
104+
| | | `notMatched`
105+
106+
<!-- TODO spec for the environment from patterns propagated to the result -->
107+
108+
109+
## Quoted Patterns transformation
110+
111+
Coming soon...

0 commit comments

Comments
 (0)