Skip to content

Commit ac58dc2

Browse files
committed
Implement Matcher runtime logic
This PR adds: * runtime logic for pattern matching on `'{ ... }` * `Literal` pattern to match a literal expression and extract its value
1 parent 5f28215 commit ac58dc2

File tree

11 files changed

+1147
-10
lines changed

11 files changed

+1147
-10
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package scala.runtime.quoted
2+
3+
import scala.annotation.internal.sharable
4+
5+
import scala.quoted._
6+
import scala.tasty._
7+
8+
object Matcher {
9+
10+
private final val debug = false
11+
12+
/**
13+
*
14+
* @param scrutineeExpr
15+
* @param patternExpr
16+
* @param reflection
17+
* @return None if it did not match, Some(tup) if it matched where tup contains Expr[_], Type[_] or Binding[_]
18+
*/
19+
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = {
20+
import reflection._
21+
22+
def treeMatches(scrutinee: Tree, pattern: Tree)(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = {
23+
24+
/** Check that both are `val` or both are `lazy val` or both are `var` **/
25+
def checkValFlags(): Boolean = {
26+
import Flags._
27+
val sFlags = scrutinee.symbol.flags
28+
val pFlags = pattern.symbol.flags
29+
sFlags.is(Lazy) == pFlags.is(Lazy) && sFlags.is(Mutable) == pFlags.is(Mutable)
30+
}
31+
32+
def isMatcherHole(sym: Symbol): Boolean =
33+
sym.owner.fullName == "scala.runtime.quoted.Matcher$" // TODO check symbol equality instead of its name
34+
35+
def treesMatch(scrutinees: List[Tree], patterns: List[Tree]): Option[Tuple] =
36+
if (scrutinees.size != patterns.size) None
37+
else foldMatchings(scrutinees.zip(patterns).map(treeMatches): _*)
38+
39+
// def bindingAndTptMatch(sym: Symbol, tpt1: TypeTree, tpt2: TypeTree): (Option[Tuple], Option[Tuple]) = tpt2 match {
40+
// case bindHole @ TypeTree.Applied(TypeIdent("bindHole"), IsTypeTree(tpt2) :: Nil)
41+
// if isMatcherHole(bindHole.symbol) && tpt1.tpe <:< tpt2.tpe => // Is the subtype check required?
42+
// val binding = new Binding(sym.name, sym)
43+
// (Some(Tuple1(binding)), treeMatches(tpt1, tpt2))
44+
// case returnTpt2 =>
45+
// (Some(()), treeMatches(tpt1, tpt2))
46+
// }
47+
48+
(scrutinee, pattern) match {
49+
// Normalize blocks without statements
50+
case (Block(Nil, expr), _) => treeMatches(expr, pattern)
51+
case (_, Block(Nil, pat)) => treeMatches(scrutinee, pat)
52+
53+
case (IsTerm(scrutinee), TypeApply(Ident("patternHole"), tpt :: Nil))
54+
if pattern.symbol.fullName == "scala.internal.Quoted$.patternHole" && scrutinee.tpe <:< tpt.tpe => // TODO add symbol to definitions
55+
Some(Tuple1(scrutinee.seal))
56+
57+
// FIXME
58+
// case (IsTypeTree(scrutinee), IsTypeTree(pattern @ TypeTree.Applied(TypeIdent("Hole"), IsTypeTree(tpt) :: Nil)))
59+
// if isMatcherHole(pattern.symbol) && scrutinee.tpe <:< tpt.tpe => // Is the subtype check required?
60+
// Some(Tuple1(scrutinee.tpe.seal))
61+
62+
case (Inlined(_, Nil, scr), _) =>
63+
treeMatches(scr, pattern)
64+
case (_, Inlined(_, Nil, pat)) =>
65+
treeMatches(scrutinee, pat)
66+
67+
case (Literal(constant1), Literal(constant2)) if constant1 == constant2 =>
68+
Some(())
69+
70+
case (Ident(_), Ident(_)) if scrutinee.symbol == pattern.symbol || env((scrutinee.symbol, pattern.symbol)) =>
71+
Some(())
72+
73+
case (Typed(expr1, tpt1), Typed(expr2, tpt2)) =>
74+
foldMatchings(treeMatches(expr1, expr2), treeMatches(tpt1, tpt2))
75+
76+
case (Select(qual1, _), Select(qual2, _)) if scrutinee.symbol == pattern.symbol =>
77+
treeMatches(qual1, qual2)
78+
79+
case (Ident(_), Select(_, _)) if scrutinee.symbol == pattern.symbol =>
80+
Some(())
81+
82+
case (Select(_, _), Ident(_)) if scrutinee.symbol == pattern.symbol =>
83+
Some(())
84+
85+
case (Apply(fn1, args1), Apply(fn2, args2)) if fn1.symbol == fn2.symbol =>
86+
foldMatchings(treeMatches(fn1, fn2), treesMatch(args1, args2))
87+
88+
case (TypeApply(fn1, args1), TypeApply(fn2, args2)) if fn1.symbol == fn2.symbol =>
89+
foldMatchings(treeMatches(fn1, fn2), treesMatch(args1, args2))
90+
91+
case (Block(stats1, expr1), Block(stats2, expr2)) =>
92+
foldMatchings(treesMatch(stats1, stats2), treeMatches(expr1, expr2))
93+
94+
case (If(cond1, thenp1, elsep1), If(cond2, thenp2, elsep2)) =>
95+
foldMatchings(treeMatches(cond1, cond2), treeMatches(thenp1, thenp2), treeMatches(elsep1, elsep2))
96+
97+
// case (Assign(lhs1, rhs1), Assign(lhs2, rhs2)) =>
98+
// val lhsMatch = lhs2 match {
99+
// case Ident("varHole") if isMatcherHole(lhs2.symbol) =>
100+
// val sym = lhs1.symbol
101+
// Some(Tuple1(new Binding(sym.name, sym)))
102+
// case _ =>
103+
// if (treeMatches(lhs1, lhs2).isDefined) Some(())
104+
// else None
105+
// }
106+
// foldMatchings(lhsMatch, treeMatches(rhs1, rhs2))
107+
108+
case (While(cond1, body1), While(cond2, body2)) =>
109+
foldMatchings(treeMatches(cond1, cond2), treeMatches(body1, body2))
110+
111+
case (NamedArg(name1, expr1), NamedArg(name2, expr2)) if name1 == name2 =>
112+
treeMatches(expr1, expr2)
113+
114+
case (New(tpt1), New(tpt2)) =>
115+
treeMatches(tpt1, tpt2)
116+
117+
case (This(_), This(_)) if scrutinee.symbol == pattern.symbol =>
118+
Some(())
119+
120+
case (Super(qual1, mix1), Super(qual2, mix2)) if mix1 == mix2 =>
121+
treeMatches(qual1, qual2)
122+
123+
case (Repeated(elems1, _), Repeated(elems2, _)) if elems1.size == elems2.size =>
124+
treesMatch(elems1, elems2)
125+
126+
case (IsTypeTree(scrutinee @ TypeIdent(_)), IsTypeTree(pattern @ TypeIdent(_))) if scrutinee.symbol == pattern.symbol =>
127+
Some(())
128+
129+
case (IsInferred(scrutinee), IsInferred(pattern)) if scrutinee.tpe <:< pattern.tpe =>
130+
Some(())
131+
132+
case (Applied(tycon1, args1), Applied(tycon2, args2)) =>
133+
foldMatchings(treeMatches(tycon1, tycon2), treesMatch(args1, args2))
134+
135+
// case (ValDef(_, tpt1, rhs1), ValDef(_, tpt2, rhs2)) if checkValFlags() =>
136+
// val (bindMatch, returnTptMatch) = bindingAndTptMatch(scrutinee.symbol, tpt1, tpt2)
137+
// val rhsEnv = env + (scrutinee.symbol -> pattern.symbol)
138+
// val rhsMatchings = treeOptMatches(rhs1, rhs2)(rhsEnv)
139+
// foldMatchings(bindMatch, returnTptMatch, rhsMatchings)
140+
141+
// case (DefDef(_, typeParams1, paramss1, tpt1, Some(rhs1)), DefDef(_, typeParams2, paramss2, tpt2, Some(rhs2))) =>
142+
// val typeParmasMatch = treesMatch(typeParams1, typeParams2)
143+
// val paramssMatch =
144+
// if (paramss1.size != paramss2.size) None
145+
// else foldMatchings(paramss1.zip(paramss2).map { (params1, params2) => treesMatch(params1, params2) }: _*)
146+
// val (bindMatch, tptMatch) = bindingAndTptMatch(scrutinee.symbol, tpt1, tpt2)
147+
// val rhsEnv =
148+
// env + (scrutinee.symbol -> pattern.symbol) ++
149+
// typeParams1.zip(typeParams2).map((tparam1, tparam2) => tparam1.symbol -> tparam2.symbol) ++
150+
// paramss1.flatten.zip(paramss2.flatten).map((param1, param2) => param1.symbol -> param2.symbol)
151+
// val rhsMatch = treeMatches(rhs1, rhs2)(rhsEnv)
152+
//
153+
// foldMatchings(bindMatch, typeParmasMatch, paramssMatch, tptMatch, rhsMatch)
154+
155+
case (Lambda(_, tpt1), Lambda(_, tpt2)) =>
156+
// TODO match tpt1 with tpt2?
157+
Some(())
158+
159+
case (Match(scru1, cases1), Match(scru2, cases2)) =>
160+
val scrutineeMacth = treeMatches(scru1, scru2)
161+
val casesMatch =
162+
if (cases1.size != cases2.size) None
163+
else foldMatchings(cases1.zip(cases2).map(caseMatches): _*)
164+
foldMatchings(scrutineeMacth, casesMatch)
165+
166+
case (Try(body1, cases1, finalizer1), Try(body2, cases2, finalizer2)) =>
167+
val bodyMacth = treeMatches(body1, body2)
168+
val casesMatch =
169+
if (cases1.size != cases2.size) None
170+
else foldMatchings(cases1.zip(cases2).map(caseMatches): _*)
171+
val finalizerMatch = treeOptMatches(finalizer1, finalizer2)
172+
foldMatchings(bodyMacth, casesMatch, finalizerMatch)
173+
174+
case _ =>
175+
if (debug)
176+
println(
177+
s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
178+
|Scrutinee
179+
| ${scrutinee.showCode}
180+
|
181+
|${scrutinee.show}
182+
|
183+
|did not match pattern
184+
| ${pattern.showCode}
185+
|
186+
|${pattern.show}
187+
|
188+
|
189+
|
190+
|
191+
|""".stripMargin)
192+
None
193+
}
194+
}
195+
196+
def treeOptMatches(scrutinee: Option[Tree], pattern: Option[Tree])(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = {
197+
(scrutinee, pattern) match {
198+
case (Some(x), Some(y)) => treeMatches(x, y)
199+
case (None, None) => Some(())
200+
case _ => None
201+
}
202+
}
203+
204+
def caseMatches(scrutinee: CaseDef, pattern: CaseDef)(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = {
205+
val (caseEnv, patternMatch) = patternMatches(scrutinee.pattern, pattern.pattern)
206+
val guardMatch = treeOptMatches(scrutinee.guard, pattern.guard)(caseEnv)
207+
val rhsMatch = treeMatches(scrutinee.rhs, pattern.rhs)(caseEnv)
208+
foldMatchings(patternMatch, guardMatch, rhsMatch)
209+
}
210+
211+
def patternMatches(scrutinee: Pattern, pattern: Pattern)(implicit env: Set[(Symbol, Symbol)]): (Set[(Symbol, Symbol)], Option[Tuple]) = (scrutinee, pattern) match {
212+
case (Pattern.Value(v1), Pattern.Unapply(TypeApply(Select(patternHole @ Ident("patternHole"), "unapply"), List(tpt)), Nil, Nil))
213+
if patternHole.symbol.owner.fullName == "scala.runtime.quoted.Matcher$" =>
214+
(env, Some(Tuple1(v1.seal)))
215+
// case (Pattern.Bind(name1, body1), Pattern.Unapply(TypeApply(Select(patternBindHole @ Ident("patternBindHole"), "unapply"), List(tpt)), Nil, List(pattern2 @ Pattern.Bind(name2, body2))))
216+
// if patternBindHole.symbol.owner.fullName == "scala.runtime.quoted.Matcher$" =>
217+
// val binding = new Binding(scrutinee.symbol.name, scrutinee.symbol)
218+
// val env1 = env + (scrutinee.symbol -> pattern2.symbol)
219+
// val (env2, bodyMatch) = patternMatches(body1, body2)(env1)
220+
// (env2, foldMatchings(Some(Tuple1(binding)), bodyMatch))
221+
case (Pattern.Value(v1), Pattern.Value(v2)) =>
222+
(env, treeMatches(v1, v2))
223+
case (Pattern.Bind(name1, body1), Pattern.Bind(name2, body2)) =>
224+
val bindEnv = env + (scrutinee.symbol -> pattern.symbol)
225+
patternMatches(body1, body2)(bindEnv)
226+
case (Pattern.Unapply(fun1, implicits1, patterns1), Pattern.Unapply(fun2, implicits2, patterns2)) =>
227+
val funMatch = treeMatches(fun1, fun2)
228+
val implicitsMatch =
229+
if (implicits1.size != implicits2.size) None
230+
else foldMatchings(implicits1.zip(implicits2).map(treeMatches): _*)
231+
val (patEnv, patternsMatch) = foldPatterns(patterns1, patterns2)
232+
(patEnv, foldMatchings(funMatch, implicitsMatch, patternsMatch))
233+
case (Pattern.Alternatives(patterns1), Pattern.Alternatives(patterns2)) =>
234+
foldPatterns(patterns1, patterns2)
235+
case (Pattern.TypeTest(tpt1), Pattern.TypeTest(tpt2)) =>
236+
(env, treeMatches(tpt1, tpt2))
237+
case _ =>
238+
if (debug)
239+
println(
240+
s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
241+
|Scrutinee
242+
| ${scrutinee.showCode}
243+
|
244+
|${scrutinee.show}
245+
|
246+
|did not match pattern
247+
| ${pattern.showCode}
248+
|
249+
|${pattern.show}
250+
|
251+
|
252+
|
253+
|
254+
|""".stripMargin)
255+
(env, None)
256+
}
257+
258+
def foldPatterns(patterns1: List[Pattern], patterns2: List[Pattern])(implicit env: Set[(Symbol, Symbol)]): (Set[(Symbol, Symbol)], Option[Tuple]) = {
259+
if (patterns1.size != patterns2.size) (env, None)
260+
else patterns1.zip(patterns2).foldLeft((env, Option[Tuple](()))) { (acc, x) =>
261+
val (env, res) = patternMatches(x._1, x._2)(acc._1)
262+
(env, foldMatchings(acc._2, res))
263+
}
264+
}
265+
266+
treeMatches(scrutineeExpr.unseal, patternExpr.unseal)(Set.empty).asInstanceOf[Option[Tup]]
267+
}
268+
269+
private def foldMatchings(matchings: Option[Tuple]*): Option[Tuple] = {
270+
matchings.foldLeft[Option[Tuple]](Some(())) {
271+
case (Some(acc), Some(holes)) => Some(acc ++ holes)
272+
case (_, _) => None
273+
}
274+
}
275+
276+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package scala.runtime.quoted
2+
3+
import scala.quoted._
4+
import scala.tasty._
5+
6+
object Matcher {
7+
8+
type Hole[T /* <: AnyKind */] = T
9+
10+
def hole[T]: T = ???
11+
def literal[T]: T = ???
12+
13+
def unapplySeq(scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Seq[Any]] =
14+
throw new Exception("running on non bootstrapped library")
15+
16+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package scala.quoted.matching
2+
3+
import scala.quoted.Expr
4+
5+
import scala.tasty.Reflection // TODO do not depend on reflection directly
6+
7+
/** Matches expressions containing literal values and extracts the value.
8+
*
9+
* Usage:
10+
* ```
11+
* (x: Expr[B]) match {
12+
* case Literal(value: B) => ...
13+
* }
14+
* ```
15+
*/
16+
object Literal {
17+
18+
def unapply[T](expr: Expr[T])(implicit reflect: Reflection): Option[T] = {
19+
import reflect.{Literal => LiteralTree, _} // TODO rename reflect.Literal to avoid this clash
20+
def literal(tree: Term): Option[T] = tree match {
21+
case LiteralTree(c) => Some(c.value.asInstanceOf[T])
22+
case Block(Nil, e) => literal(e)
23+
case Inlined(_, Nil, e) => literal(e)
24+
case _ => None
25+
}
26+
literal(expr.unseal)
27+
}
28+
29+
}

library/src/scala/runtime/quoted/Matcher.scala

Lines changed: 0 additions & 10 deletions
This file was deleted.

library/src/scala/tasty/util/ConstantExtractor.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package scala.tasty.util
33
import scala.quoted.Expr
44
import scala.tasty.Reflection
55

6+
import scala.deprecated
7+
68
/**
79
* Usage:
810
*
@@ -15,6 +17,7 @@ import scala.tasty.Reflection
1517
* }
1618
* ```
1719
*/
20+
@deprecated("Use scala.quoted.matching.Literal", "0.14.0")
1821
class ConstantExtractor[R <: Reflection with Singleton](val reflect: Reflection) {
1922
import reflect._
2023

0 commit comments

Comments
 (0)