@@ -26,31 +26,90 @@ object QuotePatterns:
26
26
import tpd ._
27
27
28
28
/** Check for restricted patterns */
29
- def checkPattern (quotePattern : QuotePattern )(using Context ): Unit = new tpd.TreeTraverser {
30
- def traverse (tree : Tree )(using Context ): Unit = tree match {
31
- case _ : SplicePattern =>
32
- case tdef : TypeDef if tdef.symbol.isClass =>
33
- val kind = if tdef.symbol.is(Module ) then " objects" else " classes"
34
- report.error(em " Implementation restriction: cannot match $kind" , tree.srcPos)
35
- case tree : NamedDefTree =>
36
- if tree.name.is(NameKinds .WildcardParamName ) then
37
- report.warning(
38
- " Use of `_` for lambda in quoted pattern. Use explicit lambda instead or use `$_` to match any term." ,
39
- tree.srcPos)
40
- if tree.name.isTermName && ! tree.nameSpan.isSynthetic && tree.name != nme.ANON_FUN && tree.name.startsWith(" $" ) then
41
- report.error(" Names cannot start with $ quote pattern" , tree.namePos)
42
- traverseChildren(tree)
43
- case _ : Match =>
44
- report.error(" Implementation restriction: cannot match `match` expressions" , tree.srcPos)
45
- case _ : Try =>
46
- report.error(" Implementation restriction: cannot match `try` expressions" , tree.srcPos)
47
- case _ : Return =>
48
- report.error(" Implementation restriction: cannot match `return` statements" , tree.srcPos)
49
- case _ =>
50
- traverseChildren(tree)
51
- }
29
+ def checkPattern (quotePattern : QuotePattern )(using Context ): Unit =
30
+ def validatePatternAndCollectTypeVars (): Set [Symbol ] = new tpd.TreeAccumulator [Set [Symbol ]] {
31
+ override def apply (typevars : Set [Symbol ], tree : tpd.Tree )(using Context ): Set [Symbol ] =
32
+ // Collect type variables
33
+ val typevars1 = tree match
34
+ case tree @ DefDef (_, paramss, _, _) =>
35
+ typevars union paramss.flatMap{ params => params match
36
+ case TypeDefs (tdefs) => tdefs.map(_.symbol)
37
+ case _ => List .empty
38
+ }.toSet union typevars
39
+ case _ => typevars
40
+
41
+ // Validate pattern
42
+ tree match
43
+ case _ : SplicePattern => typevars1
44
+ case tdef : TypeDef if tdef.symbol.isClass =>
45
+ val kind = if tdef.symbol.is(Module ) then " objects" else " classes"
46
+ report.error(em " Implementation restriction: cannot match $kind" , tree.srcPos)
47
+ typevars1
48
+ case tree : NamedDefTree =>
49
+ if tree.name.is(NameKinds .WildcardParamName ) then
50
+ report.warning(
51
+ " Use of `_` for lambda in quoted pattern. Use explicit lambda instead or use `$_` to match any term." ,
52
+ tree.srcPos)
53
+ if tree.name.isTermName && ! tree.nameSpan.isSynthetic && tree.name != nme.ANON_FUN && tree.name.startsWith(" $" ) then
54
+ report.error(" Names cannot start with $ quote pattern" , tree.namePos)
55
+ foldOver(typevars1, tree)
56
+ case _ : Match =>
57
+ report.error(" Implementation restriction: cannot match `match` expressions" , tree.srcPos)
58
+ typevars1
59
+ case _ : Try =>
60
+ report.error(" Implementation restriction: cannot match `try` expressions" , tree.srcPos)
61
+ typevars1
62
+ case _ : Return =>
63
+ report.error(" Implementation restriction: cannot match `return` statements" , tree.srcPos)
64
+ typevars1
65
+ case _ =>
66
+ foldOver(typevars1, tree)
67
+ }.apply(Set .empty, quotePattern.body)
68
+
69
+ val boundTypeVars = validatePatternAndCollectTypeVars()
52
70
53
- }.traverse(quotePattern.body)
71
+ /*
72
+ * This part checks well-formedness of arguments to hoas patterns.
73
+ * (1) Type arguments of a hoas patterns must be introduced in the quote pattern.ctxShow
74
+ * Examples
75
+ * well-formed: '{ [A] => (x : A) => $a[A](x) } // A is introduced in the quote pattern
76
+ * ill-formed: '{ (x : Int) => $a[Int](x) } // Int is defined outside of the quote pattern
77
+ * (2) If value arguments of a hoas pattern has a type with type variables that are introduced in
78
+ * the quote pattern, those type variables should be in type arguments to the hoas patternHole
79
+ * Examples
80
+ * well-formed: '{ [A] => (x : A) => $a[A](x) } // a : [A] => (x:A) => A
81
+ * ill-formed: '{ [A] => (x : A) => $a(x) } // a : (x:A) => A ...but A is undefined; hence ill-formed
82
+ */
83
+ new tpd.TreeTraverser {
84
+ override def traverse (tree : tpd.Tree )(using Context ): Unit = tree match {
85
+ case tree : SplicePattern =>
86
+ def uncapturedTypeVars (arg : tpd.Tree , capturedTypeVars : List [tpd.Tree ]): Set [Type ] =
87
+ /* Sometimes arg is untyped when a splice pattern is ill-formed.
88
+ * Return early in such case.
89
+ * Refer to QuoteAndSplices::typedSplicePattern
90
+ */
91
+ if ! arg.hasType then return Set .empty
92
+
93
+ val capturedTypeVarsSet = capturedTypeVars.map(_.symbol).toSet
94
+ new TypeAccumulator [Set [Type ]] {
95
+ def apply (x : Set [Type ], tp : Type ): Set [Type ] =
96
+ if boundTypeVars.contains(tp.typeSymbol) && ! capturedTypeVarsSet.contains(tp.typeSymbol) then
97
+ foldOver(x + tp, tp)
98
+ else
99
+ foldOver(x, tp)
100
+ }.apply(Set .empty, arg.tpe)
101
+
102
+ for (typearg <- tree.typeargs) // case (1)
103
+ do
104
+ if ! boundTypeVars.contains(typearg.symbol) then
105
+ report.error(" Type arguments of a hoas pattern needs to be defined inside the quoted pattern" , typearg.srcPos)
106
+ for (arg <- tree.args) // case (2)
107
+ do
108
+ if ! uncapturedTypeVars(arg, tree.typeargs).isEmpty then
109
+ report.error(" Type variables that this argument depends on are not captured in this hoas pattern" , arg.srcPos)
110
+ case _ => traverseChildren(tree)
111
+ }
112
+ }.traverse(quotePattern.body)
54
113
55
114
/** Encode the quote pattern into an `unapply` that the pattern matcher can handle.
56
115
*
@@ -74,7 +133,7 @@ object QuotePatterns:
74
133
* .ExprMatch // or TypeMatch
75
134
* .unapply[
76
135
* KCons[t1 >: l1 <: b1, ...KCons[tn >: ln <: bn, KNil]...], // scala.quoted.runtime.{KCons, KNil}
77
- * (T1, T2, (A1, ..., An) => T3, ...)
136
+ * (Expr[T1], Expr[T2], Expr[ (A1, ..., An) => T3] , ...)
78
137
* ](
79
138
* '{
80
139
* type t1' >: l1' <: b1'
@@ -197,16 +256,24 @@ object QuotePatterns:
197
256
val patBuf = new mutable.ListBuffer [Tree ]
198
257
val shape = new tpd.TreeMap {
199
258
override def transform (tree : Tree )(using Context ) = tree match {
200
- case Typed (splice @ SplicePattern (pat, Nil ), tpt) if ! tpt.tpe.derivesFrom(defn.RepeatedParamClass ) =>
259
+ case Typed (splice @ SplicePattern (pat, Nil , Nil ), tpt) if ! tpt.tpe.derivesFrom(defn.RepeatedParamClass ) =>
201
260
transform(tpt) // Collect type bindings
202
261
transform(splice)
203
- case SplicePattern (pat, args) =>
262
+ case SplicePattern (pat, typeargs, args) =>
204
263
val patType = pat.tpe.widen
205
264
val patType1 = patType.translateFromRepeated(toArray = false )
206
265
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
207
266
patBuf += pat1
208
- if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole .termRef).appliedToType(tree.tpe).withSpan(tree.span)
209
- else ref(defn.QuotedRuntimePatterns_higherOrderHole .termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral (args, TypeTree (defn.AnyType ))).withSpan(tree.span)
267
+ if typeargs.isEmpty && args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole .termRef).appliedToType(tree.tpe).withSpan(tree.span)
268
+ else if typeargs.isEmpty then
269
+ ref(defn.QuotedRuntimePatterns_higherOrderHole .termRef)
270
+ .appliedToType(tree.tpe)
271
+ .appliedTo(SeqLiteral (args, TypeTree (defn.AnyType )))
272
+ .withSpan(tree.span)
273
+ else ref(defn.QuotedRuntimePatterns_higherOrderHoleWithTypes .termRef)
274
+ .appliedToTypeTrees(List (TypeTree (tree.tpe), tpd.hkNestedPairsTypeTree(typeargs)))
275
+ .appliedTo(SeqLiteral (args, TypeTree (defn.AnyType )))
276
+ .withSpan(tree.span)
210
277
case _ =>
211
278
super .transform(tree)
212
279
}
@@ -232,17 +299,19 @@ object QuotePatterns:
232
299
fun match
233
300
// <quotes>.asInstanceOf[QuoteMatching].{ExprMatch,TypeMatch}.unapply[<typeBindings>, <resTypes>]
234
301
case TypeApply (Select (Select (TypeApply (Select (quotes, _), _), _), _), typeBindings :: resTypes :: Nil ) =>
235
- val bindings = unrollBindings (typeBindings)
302
+ val bindings = unrollHkNestedPairsTypeTree (typeBindings)
236
303
val addPattenSplice = new TreeMap {
237
304
private val patternIterator = patterns.iterator.filter {
238
305
case pat : Bind => ! pat.symbol.name.is(PatMatGivenVarName )
239
306
case _ => true
240
307
}
241
308
override def transform (tree : tpd.Tree )(using Context ): tpd.Tree = tree match
242
309
case TypeApply (patternHole, _) if patternHole.symbol == defn.QuotedRuntimePatterns_patternHole =>
243
- cpy.SplicePattern (tree)(patternIterator.next(), Nil )
310
+ cpy.SplicePattern (tree)(patternIterator.next(), Nil , Nil )
244
311
case Apply (patternHole, SeqLiteral (args, _) :: Nil ) if patternHole.symbol == defn.QuotedRuntimePatterns_higherOrderHole =>
245
- cpy.SplicePattern (tree)(patternIterator.next(), args)
312
+ cpy.SplicePattern (tree)(patternIterator.next(), Nil , args)
313
+ case Apply (TypeApply (patternHole, List (_, targsTpe)), SeqLiteral (args, _) :: Nil ) if patternHole.symbol == defn.QuotedRuntimePatterns_higherOrderHoleWithTypes =>
314
+ cpy.SplicePattern (tree)(patternIterator.next(), unrollHkNestedPairsTypeTree(targsTpe), args)
246
315
case _ => super .transform(tree)
247
316
}
248
317
val body = addPattenSplice.transform(shape) match
@@ -260,7 +329,7 @@ object QuotePatterns:
260
329
case body => body
261
330
cpy.QuotePattern (tree)(bindings, body, quotes)
262
331
263
- private def unrollBindings (tree : Tree )(using Context ): List [Tree ] = tree match
332
+ private def unrollHkNestedPairsTypeTree (tree : Tree )(using Context ): List [Tree ] = tree match
264
333
case AppliedTypeTree (tupleN, bindings) if defn.isTupleClass(tupleN.symbol) => bindings // TupleN, 1 <= N <= 22
265
- case AppliedTypeTree (_, head :: tail :: Nil ) => head :: unrollBindings (tail) // KCons or *:
334
+ case AppliedTypeTree (_, head :: tail :: Nil ) => head :: unrollHkNestedPairsTypeTree (tail) // KCons or *:
266
335
case _ => Nil // KNil or EmptyTuple
0 commit comments