Skip to content

Commit ca27ceb

Browse files
committed
Refactor quote matching internal logic
Aimed to simplify the logic exposed by the CompilerInterface
1 parent ff7068f commit ca27ceb

File tree

11 files changed

+102
-101
lines changed

11 files changed

+102
-101
lines changed

compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,20 +2546,62 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext:
25462546
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree =
25472547
PickledQuotes.unpickleType(repr, args)
25482548

2549-
def Constraints_context[T]: scala.quoted.QuoteContext =
2550-
val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(dotc.core.Mode.GadtConstraintInference)
2551-
dotty.tools.dotc.quoted.QuoteContextImpl()(using ctx1)
2549+
def termMatch(scrutinee: Term, pattern: Term): Option[Tuple] =
2550+
treeMatch(scrutinee, pattern)
2551+
2552+
def typeTreeMatch(scrutinee: TypeTree, pattern: TypeTree): Option[Tuple] =
2553+
treeMatch(scrutinee, pattern)
2554+
2555+
private def treeMatch(scrutinee: Tree, pattern: Tree): Option[Tuple] = {
2556+
def isTypeHoleDef(tree: Tree): Boolean =
2557+
tree match
2558+
case tree: TypeDef =>
2559+
tree.symbol.hasAnnotation(dotc.core.Symbols.defn.InternalQuotedMatcher_patternTypeAnnot)
2560+
case _ => false
2561+
2562+
def extractTypeHoles(pat: Term): (Term, List[Symbol]) =
2563+
pat match
2564+
case tpd.Inlined(_, Nil, pat2) => extractTypeHoles(pat2)
2565+
case tpd.Block(stats @ ((typeHole: TypeDef) :: _), expr) if isTypeHoleDef(typeHole) =>
2566+
val holes = stats.takeWhile(isTypeHoleDef).map(_.symbol)
2567+
val otherStats = stats.dropWhile(isTypeHoleDef)
2568+
(tpd.cpy.Block(pat)(otherStats, expr), holes)
2569+
case _ =>
2570+
(pat, Nil)
25522571

2553-
def Constraints_add(syms: List[Symbol]): Boolean =
2554-
ctx.gadt.addToConstraint(syms)
2572+
val (pat1, typeHoles) = extractTypeHoles(pattern)
25552573

2556-
def Constraints_approximation(sym: Symbol, fromBelow: Boolean): Type =
2557-
ctx.gadt.approximation(sym, fromBelow)
2574+
val ctx1 =
2575+
if typeHoles.isEmpty then ctx
2576+
else
2577+
val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(dotc.core.Mode.GadtConstraintInference)
2578+
ctx1.gadt.addToConstraint(typeHoles)
2579+
ctx1
2580+
2581+
val qctx1 = dotty.tools.dotc.quoted.QuoteContextImpl()(using ctx1)
2582+
.asInstanceOf[QuoteContext { val tasty: QuoteContextImpl.this.tasty.type }]
2583+
2584+
val matcher = new scala.internal.quoted.Matcher.QuoteMatcher[qctx1.type](qctx1)
2585+
2586+
val matchings =
2587+
if pat1.isType then matcher.termMatch(scrutinee, pat1)
2588+
else matcher.termMatch(scrutinee, pat1)
2589+
2590+
// val matchings = matcher.termMatch(scrutinee, pattern)
2591+
if typeHoles.isEmpty then matchings
2592+
else {
2593+
// After matching and doing all subtype checks, we have to approximate all the type bindings
2594+
// that we have found, seal them in a quoted.Type and add them to the result
2595+
def typeHoleApproximation(sym: Symbol) =
2596+
ctx1.gadt.approximation(sym, !sym.hasAnnotation(dotc.core.Symbols.defn.InternalQuotedMatcher_fromAboveAnnot)).seal
2597+
matchings.map { tup =>
2598+
Tuple.fromIArray(typeHoles.map(typeHoleApproximation).toArray.asInstanceOf[IArray[Object]]) ++ tup
2599+
}
2600+
}
2601+
}
25582602

25592603
def Definitions_InternalQuotedMatcher_patternHole: Symbol = dotc.core.Symbols.defn.InternalQuotedMatcher_patternHole
25602604
def Definitions_InternalQuotedMatcher_higherOrderHole: Symbol = dotc.core.Symbols.defn.InternalQuotedMatcher_higherOrderHole
2561-
def Definitions_InternalQuotedMatcher_patternTypeAnnot: Symbol = dotc.core.Symbols.defn.InternalQuotedMatcher_patternTypeAnnot
2562-
def Definitions_InternalQuotedMatcher_fromAboveAnnot: Symbol = dotc.core.Symbols.defn.InternalQuotedMatcher_fromAboveAnnot
25632605

25642606
def betaReduce(tree: Term): Option[Term] =
25652607
tree match

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ trait QuotesAndSplices {
450450
else ref(defn.QuotedTypeModule_apply.termRef).appliedToTypeTree(shape).select(nme.apply).appliedTo(qctx)
451451
UnApply(
452452
fun = ref(unapplySym.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
453-
implicits = quotedPattern :: Literal(Constant(typeBindings.nonEmpty)) :: qctx :: Nil,
453+
implicits = quotedPattern :: qctx :: Nil,
454454
patterns = splicePat :: Nil,
455455
proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
456456
}

library/src-bootstrapped/scala/internal/quoted/Expr.scala

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,14 @@ object Expr {
4747
*
4848
* @param scrutineeExpr `Expr[Any]` on which we are pattern matching
4949
* @param patternExpr `Expr[Any]` containing the pattern tree
50-
* @param hasTypeSplices `Boolean` notify if the pattern has type splices (if so we use a GADT context)
50+
* @param hasTypeSplices `Boolean` notify if the pattern has type splices
5151
* @param qctx the current QuoteContext
5252
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]``
5353
*/
54-
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: scala.quoted.Expr[Any])(using patternExpr: scala.quoted.Expr[Any],
55-
hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = {
54+
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: scala.quoted.Expr[Any])
55+
(using patternExpr: scala.quoted.Expr[Any], qctx: QuoteContext): Option[Tup] = {
5656
val qctx1 = quoteContextWithCompilerInterface(qctx)
57-
val qctx2 = if hasTypeSplices then qctx1.tasty.Constraints_context else qctx1
58-
given qctx2.type = qctx2
59-
new Matcher.QuoteMatcher[qctx2.type](qctx2).termMatch(scrutineeExpr.unseal, patternExpr.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
57+
qctx1.tasty.termMatch(scrutineeExpr.unseal, patternExpr.unseal).asInstanceOf[Option[Tup]]
6058
}
6159

6260
/** Returns a null expresssion equivalent to `'{null}` */

library/src-bootstrapped/scala/internal/quoted/Matcher.scala

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -145,54 +145,13 @@ object Matcher {
145145

146146
inline private def withEnv[T](env: Env)(inline body: Env ?=> T): T = body(using env)
147147

148-
class SymBinding(val sym: Symbol, val fromAbove: Boolean)
149-
150-
def termMatch(scrutineeTerm: Term, patternTerm: Term, hasTypeSplices: Boolean): Option[Tuple] = {
148+
def termMatch(scrutineeTerm: Term, patternTerm: Term): Option[Tuple] =
151149
given Env = Map.empty
152-
val matchings = scrutineeTerm =?= patternTerm
153-
if !hasTypeSplices then matchings
154-
else {
155-
// After matching and doing all subtype checks, we have to approximate all the type bindings
156-
// that we have found and seal them in a quoted.Type
157-
matchings.asOptionOfTuple.map { tup =>
158-
Tuple.fromArray(tup.toArray.map { // TODO improve performance
159-
case x: SymBinding => qctx.tasty.Constraints_approximation(x.sym, !x.fromAbove).seal
160-
case x => x
161-
})
162-
}
163-
}
164-
}
150+
scrutineeTerm =?= patternTerm
165151

166-
// TODO factor out common logic with `termMatch`
167-
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree, hasTypeSplices: Boolean): Option[Tuple] = {
152+
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree): Option[Tuple] =
168153
given Env = Map.empty
169-
val matchings = scrutineeTypeTree =?= patternTypeTree
170-
if !hasTypeSplices then matchings
171-
else {
172-
// After matching and doing all subtype checks, we have to approximate all the type bindings
173-
// that we have found and seal them in a quoted.Type
174-
matchings.asOptionOfTuple.map { tup =>
175-
Tuple.fromArray(tup.toArray.map { // TODO improve performance
176-
case x: SymBinding => qctx.tasty.Constraints_approximation(x.sym, !x.fromAbove).seal
177-
case x => x
178-
})
179-
}
180-
}
181-
}
182-
183-
private def hasPatternTypeAnnotation(sym: Symbol) = sym.annots.exists(isPatternTypeAnnotation)
184-
185-
private def hasFromAboveAnnotation(sym: Symbol) = sym.annots.exists(isFromAboveAnnotation)
186-
187-
private def isPatternTypeAnnotation(tree: Tree): Boolean = tree match {
188-
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
189-
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
190-
}
191-
192-
private def isFromAboveAnnotation(tree: Tree): Boolean = tree match {
193-
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
194-
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
195-
}
154+
scrutineeTypeTree =?= patternTypeTree
196155

197156
/** Check that all trees match with `mtch` and concatenate the results with &&& */
198157
private def matchLists[T](l1: List[T], l2: List[T])(mtch: (T, T) => Matching): Matching = (l1, l2) match {
@@ -317,10 +276,6 @@ object Matcher {
317276
case (TypeApply(fn1, args1), TypeApply(fn2, args2)) =>
318277
fn1 =?= fn2 &&& args1 =?= args2
319278

320-
case (Block(stats1, expr1), Block(binding :: stats2, expr2)) if isTypeBinding(binding) =>
321-
qctx.tasty.Constraints_add(binding.symbol :: Nil)
322-
matched(new SymBinding(binding.symbol, hasFromAboveAnnotation(binding.symbol))) &&& Block(stats1, expr1) =?= Block(stats2, expr2)
323-
324279
/* Match block */
325280
case (Block(stat1 :: stats1, expr1), Block(stat2 :: stats2, expr2)) =>
326281
val newEnv = (stat1, stat2) match {
@@ -333,11 +288,6 @@ object Matcher {
333288
stat1 =?= stat2 &&& Block(stats1, expr1) =?= Block(stats2, expr2)
334289
}
335290

336-
case (scrutinee, Block(typeBindings, expr2)) if typeBindings.forall(isTypeBinding) =>
337-
val bindingSymbols = typeBindings.map(_.symbol)
338-
qctx.tasty.Constraints_add(bindingSymbols)
339-
bindingSymbols.foldRight(scrutinee =?= expr2)((x, acc) => matched(new SymBinding(x, hasFromAboveAnnotation(x))) &&& acc)
340-
341291
/* Match if */
342292
case (If(cond1, thenp1, elsep1), If(cond2, thenp2, elsep2)) =>
343293
cond1 =?= cond2 &&& thenp1 =?= thenp2 &&& elsep1 =?= elsep2
@@ -456,10 +406,6 @@ object Matcher {
456406
}
457407
}
458408

459-
private def isTypeBinding(tree: Tree): Boolean = tree match {
460-
case tree: TypeDef => hasPatternTypeAnnotation(tree.symbol)
461-
case _ => false
462-
}
463409
}
464410

465411
/** Result of matching a part of an expression */

library/src-bootstrapped/scala/internal/quoted/Type.scala

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,16 @@ object Type {
2828
/** Pattern matches an the scrutineeType against the patternType and returns a tuple
2929
* with the matched holes if successful.
3030
*
31-
* Holes:
32-
* - scala.internal.Quoted.patternHole[T]: hole that matches an expression `x` of type `Type[U]`
33-
* if `U <:< T` and returns `x` as part of the match.
34-
*
3531
* @param scrutineeType `Type[_]` on which we are pattern matching
3632
* @param patternType `Type[_]` containing the pattern tree
37-
* @param hasTypeSplices `Boolean` notify if the pattern has type splices (if so we use a GADT context)
33+
* @param hasTypeSplices `Boolean` notify if the pattern has type splices
3834
* @param qctx the current QuoteContext
3935
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Type[Ti]``
4036
*/
41-
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeType: scala.quoted.Type[_])(using patternType: scala.quoted.Type[_],
42-
hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = {
37+
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeType: scala.quoted.Type[_])
38+
(using patternType: scala.quoted.Type[_], qctx: QuoteContext): Option[Tup] = {
4339
val qctx1 = quoteContextWithCompilerInterface(qctx)
44-
val qctx2 = if hasTypeSplices then qctx1.tasty.Constraints_context else qctx1
45-
given qctx2.type = qctx2
46-
new Matcher.QuoteMatcher[qctx2.type](qctx2).typeTreeMatch(scrutineeType.unseal, patternType.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
40+
qctx1.tasty.typeTreeMatch(scrutineeType.unseal, patternType.unseal).asInstanceOf[Option[Tup]]
4741
}
4842

4943

library/src-bootstrapped/scala/quoted/Expr.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ abstract class Expr[+T] private[scala] {
2323
* ```
2424
*/
2525
final def matches(that: Expr[Any])(using qctx: QuoteContext): Boolean =
26-
!scala.internal.quoted.Expr.unapply[EmptyTuple, EmptyTuple](this)(using that, false, qctx).isEmpty
26+
!scala.internal.quoted.Expr.unapply[EmptyTuple, EmptyTuple](this)(using that, qctx).isEmpty
2727

2828
/** Checked cast to a `quoted.Expr[U]` */
2929
def cast[U](using tp: scala.quoted.Type[U])(using qctx: QuoteContext): scala.quoted.Expr[U] = asExprOf[U]

library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ object Matcher {
3232
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.fromAbove`")
3333
class fromAbove extends Annotation
3434

35-
class QuoteMatcher[QCtx <: QuoteContext & Singleton](using val qctx: QCtx) {
35+
class QuoteMatcher[QCtx <: QuoteContext & Singleton](val qctx: QCtx) {
3636
import qctx.tasty._
3737

38-
def termMatch(scrutineeTerm: Term, patternTerm: Term, hasTypeSplices: Boolean): Option[Tuple] =
38+
class SymBinding(val sym: Symbol, val fromAbove: Boolean)
39+
40+
def termMatch(scrutineeTerm: Term, patternTerm: Term): Option[Tuple] =
3941
throw new Exception("Non bootstrapped lib")
4042

41-
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree, hasTypeSplices: Boolean): Option[Tuple] =
43+
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree): Option[Tuple] =
4244
throw new Exception("Non bootstrapped lib")
4345
}
4446

library/src/scala/internal/tasty/CompilerInterface.scala

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,35 @@ trait CompilerInterface { self: scala.tasty.Reflection =>
2121
*/
2222
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledArgs): TypeTree
2323

24-
25-
def Constraints_context[T]: scala.quoted.QuoteContext
26-
def Constraints_add(syms: List[Symbol]): Boolean
27-
def Constraints_approximation(sym: Symbol, fromBelow: Boolean): Type
24+
/** Pattern matches an the scrutinee against the pattern and returns a tuple
25+
* with the matched holes if successful.
26+
*
27+
* Examples:
28+
* - `termMatch(< f(0, myInt) >, < f(0, myInt) >, false)`
29+
* will return `Some(())` (where `()` is a tuple of arity 0)
30+
* - `termMatch(< f(0, myInt) >, < f(patternHole[Int], patternHole[Int]) >, false)`
31+
* will return `Some(Tuple2('{0}, '{ myInt }))`
32+
* - `termMatch(< f(0, "abc") >, < f(0, patternHole[Int]) >, false)`
33+
* will return `None` due to the missmatch of types in the hole
34+
*
35+
* Holes:
36+
* - scala.internal.Quoted.patternHole[T]: hole that matches an expression `x` of type `Expr[U]`
37+
* if `U <:< T` and returns `x` as part of the match.
38+
*
39+
* @param scrutinee `Term` on which we are pattern matching
40+
* @param pattern `Term` containing the pattern tree
41+
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Term``
42+
*/
43+
def termMatch(scrutinee: Term, pattern: Term): Option[Tuple]
44+
45+
/** Pattern matches an the scrutineeType against the patternType and returns a tuple
46+
* with the matched holes if successful.
47+
*
48+
* @param scrutinee `TypeTree` on which we are pattern matching
49+
* @param pattern `TypeTree` containing the pattern tree
50+
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `quoted.Type[Ti]``
51+
*/
52+
def typeTreeMatch(scrutinee: TypeTree, pattern: TypeTree): Option[Tuple]
2853

2954
//
3055
// TYPES
@@ -36,12 +61,6 @@ trait CompilerInterface { self: scala.tasty.Reflection =>
3661
/** Symbol of scala.internal.CompileTime.higherOrderHole */
3762
def Definitions_InternalQuotedMatcher_higherOrderHole: Symbol
3863

39-
/** Symbol of scala.internal.CompileTime.patternType */
40-
def Definitions_InternalQuotedMatcher_patternTypeAnnot: Symbol
41-
42-
/** Symbol of scala.internal.CompileTime.fromAbove */
43-
def Definitions_InternalQuotedMatcher_fromAboveAnnot: Symbol
44-
4564
/** Returns Some with a beta-reduced application or None */
4665
def betaReduce(tree: Term): Option[Term]
4766

library/src/scala/tasty/reflect/SourceCodePrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ class SourceCodePrinter[R <: Reflection & Singleton](val tasty: R)(syntaxHighlig
583583
def printFlatBlock(stats: List[Statement], expr: Term)(using elideThis: Option[Symbol]): Buffer = {
584584
val (stats1, expr1) = flatBlock(stats, expr)
585585
val stats2 = stats1.filter {
586-
case tree: TypeDef => !tree.symbol.annots.exists(_.symbol.owner == Symbol.requiredClass("scala.internal.Quoted.quoteTypeTag"))
586+
case tree: TypeDef => !tree.symbol.annots.exists(_.symbol.maybeOwner == Symbol.requiredClass("scala.internal.Quoted.quoteTypeTag"))
587587
case _ => true
588588
}
589589
if (stats2.isEmpty) {

tests/run-macros/quote-matcher-runtime/quoted_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Macros {
77
private def impl[A, B](a: Expr[A], b: Expr[B])(using qctx: QuoteContext) : Expr[Unit] = {
88
import qctx.tasty._
99

10-
val res = scala.internal.quoted.Expr.unapply[Tuple, Tuple](a)(using b, true, qctx).map { tup =>
10+
val res = scala.internal.quoted.Expr.unapply[Tuple, Tuple](a)(using b, qctx).map { tup =>
1111
tup.toArray.toList.map {
1212
case r: Expr[_] =>
1313
s"Expr(${r.unseal.show})"

0 commit comments

Comments
 (0)