Skip to content

Commit 484ffc2

Browse files
committed
Add spec for scala.internal.quoted.Matcher.unapply
1 parent 58d5dca commit 484ffc2

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 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 below
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 the environment from patterns propagated to the result -->
106+
107+
108+
## Quoted Patterns transformation
109+
110+
Coming soon...

0 commit comments

Comments
 (0)