|
| 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