Skip to content

Commit 6d1cec1

Browse files
committed
Add spec for scala.internal.quoted.Matcher.unapply
1 parent 5c11511 commit 6d1cec1

File tree

1 file changed

+83
-46
lines changed

1 file changed

+83
-46
lines changed

docs/docs/reference/other-new-features/quoted-pattern-spec.md

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,111 @@ title: "Pattern Matching on Quoted Code"
44
---
55

66

7-
87
## Overview
98

9+
Any top level quote `'{ ... }` in a pattern position will become a quoted pattern. Inside quoteted pattern parts of the code can be spliced with `$` which extracts that part of the code.
10+
Splices ca be of two forms:
11+
* A splice `${ ... : Expr[T] }` that can be places in any expression position.
12+
* A splice `${ ... : Bind[T] }` that can be placed on names of `val`s, `var`s or `def`s
13+
1014
```scala
1115
def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match {
12-
case '{ val $a = $x; (${Bind(`a`)}: Int) + ($y: Int) } => '{ $x + $y } // TODO needs fix for #6328, `a` is currently not in scope while typing
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
1317
}
1418
```
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 urring 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 whitness at runtime.
1522

1623
```scala
1724
def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match {
18-
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] + patternHole[Int] }) =>
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 }) =>
1926
}
2027
```
2128

2229

23-
## Matcher
30+
## Runtime semantics
2431

2532
At runtime to a `quoted.Expr` can be matched to another using `scala.internal.quoted.Matcher.unapply`.
2633

2734
```scala
2835
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup]
2936
```
3037

31-
The `scrutineeExpr` is a normal quoted expression while `patternExpr` may contain holes representing splices.
38+
The `scrutineeExpr` is a normal quoted expression while `patternExpr` may contain holes representing splices.
3239
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.
3340

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 noMatch = None
47+
def emptyMatch = Some(()) // aka Some(Tuple0())
48+
def match[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(emptyMatch)(_ ++ _)
51+
52+
def matches(scrutinee: Tree, pattern: Tree) given Env: Matching // described by cases in the tables bellow
53+
54+
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
55+
56+
def equivalent(s1: Symbol, s2: Symbol) given Env: Env
57+
```
58+
59+
The implementation of `matches`
60+
61+
| Tree | Pattern | Returns |
62+
| :-----------------------: | :-------------------------: | :---------- |
63+
| Term `a` | `patternHole[X]` | `match(quoted.Expr[X]('a))` if type of `a` is a subtype of `X`
64+
| `val a: A` | `@patternBindHole val x: X` | `match(quoted.Bind[X](a.sym)) ++ matches('{val a: A}, '{val x: X})`
65+
| Literal `a` | Literal `x` | `emptyMatch` if value of `a` is equal to the value of `x`
66+
| `a` | `x` | `emptyMatch` if `equivalent(a.sym, x.sym)`
67+
| `a.b` | `x.y` | `matches('a, 'x)` if `equivalent(b.sym, y.sym)`
68+
| `a: A` | `x: X` | `matches('a, 'x) ++ matches('[A], '[X])`
69+
| `fa(.. ai ..)` | `fx(.. xi ..)` | `matches('fa, 'fx) ++ fold(.. matches('ai, 'xi) ..)`
70+
| `fa[.. Ai ..]` | `fx[.. Xi ..]` | `matches('fa, 'fx) ++ fold(.. matches('[Ai], '[Xi]) ..)`
71+
| `{.. ai ..}` | `{.. xi ..}` | `fold(.. matches('ai, 'xi) ..)`
72+
| `if (a) b else c` | `if (x) y else z` | `matches('a, 'x) ++ matches('b, 'y) ++ matches('c, 'z)`
73+
| `while (a) b` | `while (x) y` | `matches('a, 'x) ++ matches('b, 'y)`
74+
| Assignment `a = b` | Assignment `x = y` | `matches('b, 'y)` if `matches('a, 'x).nonEmpty`
75+
| Named argument<br>`n = a` | Named argument<br>`m = x` | `matches('a, 'x)`
76+
| `Seq(.. ai ..): _*` | `Seq(.. xi ..): _*` | `fold(.. matches('ai, 'xi) ..)`
77+
| `new A` | `new X` | `matches('[A], '[X])`
78+
| `this` | `this` | `emptyMatch` if both refer to the same symbol
79+
| `a.super[B]` | `x.super[Y]` | `matches('a, 'x)` if `B` equals `Y`
80+
| `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` | `matches('[A], '[X]) ++ matches('b, 'y) given envWith(a.sym -> b.sym)`
81+
| `def a[..Ai..](.. bij: Bij ..): A = c` | `def x[..Xi..](.. yij: Yij ..): X = z` | `fold(..matches('[Ai], '[Xi])..) ++ fold(.. matches('bij, 'yij) ++ matches('[Bij], '[Yij]) ..) ++ matches('[A], '[X]) ++ matches('c, 'z) given envWith(a.sym -> b.sym, .. bij.sym -> yij.sym ..)`
82+
| `(.. ai: Ai ..) => b` | `(.. xi: Xi ..) => y` | `fold(.. matches('ai, 'xi) ++ matches('[Ai], '[Xi]) ..) ++ matches('b, 'y) given envWith(.. ai.sym -> xi.sym ..)`
83+
| `a match { .. bi .. }` | `x match { .. yi .. }` | `matches('a, 'x) ++ fold(.. matches('bi, 'yi) ..)`
84+
| `try a catch { .. bi .. } finally ci` | `try x catch { .. yi .. } finally z` | `matches('a, 'x) ++ fold(.. matches('bi, 'yi) ..) ++ matches('c, 'z)`
85+
| | |
86+
| `case a if b => c` | `case x if y => z` | `matches('a, 'x) ++ matches('b, 'y) ++ matches(c, z)`
87+
| | |
88+
| Inferred `A` | Inferred `X` | `emptyMatch` if `A <:< X`
89+
| `A[.. Bi ..]` | `X[.. Yi ..]` | `emptyMatch` if `(matches('[A], '[X]) ++ fold(.. matches('[Bi], '[Yi]) ..)).nonEmpty`
90+
| `A @annot` | `X` | `matches('[A], '[X])`
91+
| `A` | `X @annot` | `matches('[A], '[X])`
92+
| | | `noMatch`
93+
94+
95+
| Pattern inside the quote | Pattern | Returns |
96+
| :-------------------------: |:--------------------------: | :------------- |
97+
| Value `a` | Value `x` | `matches('a, 'x)`
98+
| `a: A` | `x: X` | `matches('[A], '[X])`
99+
| `a @ b` | `x @ y` | `matches('b, 'y) given envWith(a.sym -> b.sym)`
100+
| Unapply `a(..bi..)(..ci..)` | Unapply `x(..yi..)(..zi..)` | `matches('a, 'x) ++ fold(.. matches('bi, 'yi) ..) ++ fold(.. matches('ci, 'zi) ..)`
101+
| `.. | ai | ..` | `.. | xi | ..` | `fold(.. matches('ai, 'xi) ..)`
102+
| `_` | `_` | `emptyMatch`
103+
| | | `noMatch`
104+
105+
<!-- TODO spec for environment from patterns propagated to the result -->
106+
107+
108+
## Quoted Patterns transformation
109+
110+
Coming soon...
111+
34112

35-
| Expression | Pattern | |
36-
| :-----------------------: |:-----------------------: | -------------------------------------------------------------------------: |
37-
| Literal `a` | Literal `x` | matches if `a == x` |
38-
| `a` | `x` | matches if `a` and `x` refer to the same symbol |
39-
| `a.b` | `x.y` | matches if `b` and `y` refer to the same symbol and `a` matches `x` |
40-
| `a: A` | `x: X` | matches if `a` matches `x` and `A` matches `X` |
41-
| `fa(.. bi ..)` | `fx(.. xi ..)` | matches if `fa` matches `fx` and for all `i` `ai` matches `xi` |
42-
| `fa[.. Ai ..]` | `fx[.. Xi ..]` | matches if `fa` matches `fx` and for all `i` `Ai` matches `Xi` |
43-
| `{.. ai ..}` | `{.. xi ..}` | matches if all `i` `ai` matches `xi` |
44-
| `if (a) b else c` | `if (x) y else z` | matches if `a` matches `x`, `b` matches `y` and `c` matches `z` |
45-
| `while (a) b` | `while (x) y` | matches if `a` matches `x` and `b` matches `y` |
46-
| Assignment `a = b` | Assignment `x = y` | matches if `a` matches `x`, `b` matches `y` |
47-
| Named argument `n = a` | Named argument `m = x | matches if `c1` matches `c2`, `t1` matches `t2` and `e1` matches `e2` |
48-
| `new A` | `new X` | matches if `A` matches `B` |
49-
| `this` | `this` | matches if both refer to the same symbol |
50-
| `a.super[B]` | `x.super[Y]` | matches if `a` matches `x` and `A` equals `Y` |
51-
| Repeated `ai` | Repeated `xi` | matches if for all `i` `ai` matches `xi` |
52-
> ValDef
53-
> DefDef
54-
> Lambda
55-
> Match
56-
> Try
57-
58-
| Type | Pattern | |
59-
| :-----------------------: |:-----------------------: | -------------------------------------------------------------------------: |
60-
| Inferred `A` | Inferred `X` | matches if `A <:< B` |
61-
> Applied Type
62-
> Annotated
63-
64-
65-
| Pattern inside the quote | Pattern | |
66-
| :-------------------------: |:--------------------------: | ---------------------------------------------------------------------------------: |
67-
| Value `a` | Value `x` | matches if `a` matches `x` |
68-
| `a: A` | `x: X` | matches if `A` matches `X` |
69-
| `a @ b` | `x @ y` | makes symbols of `a` and `x` equivalent and matches if `b` matches `y` |
70-
| Unapply `a(..bi..)(..ci..)` | Unapply `x(..yi..)(..zi..)` | matches if `a` matches `x` and for all `i` `bi` matches `yi` and `ci` matches `zi` |
71-
| `.. | ai | ..` | `.. | xi | ..` | matches if for all `i` `ai` matches `xi` |
72-
| `_` | `_` | always matches |
73-
74-
75-
## Quoted Patterns
76113

77114

0 commit comments

Comments
 (0)