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