Skip to content

Commit dac2953

Browse files
committed
Fix Reflection wildcard abstraction
Currently, we are missing in the API the Wildcard concept for `case _ =>` patterns. These are encoded as term `Ident` with name `_`. This tree can be inconsistently matched by `Ident`, `TypeIdent` or `WildcardTypeTree`. There is also no way to create an `Ident(_)`. The typed expression can contain non-Term trees: Wildcard, Alternatives, Bind and Unapply. The solution is to add a TypedTree supertype of Typed that contains a Tree. Changes * `Ident` does not match `Ident(_)` * `TypeIdent` does not match `Ident(_)` * `WildcardTypeTree` does not match `Ident(_)` if it is a term * Add `Wildcard` type that matches a term `Ident(_)` * `Typed` only matched if the expr is a `Term` * Add `TypedTree` Fixes #12188
1 parent 75edcf8 commit dac2953

File tree

18 files changed

+255
-45
lines changed

18 files changed

+255
-45
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+54-4
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,19 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
333333

334334
object TermTypeTest extends TypeTest[Tree, Term]:
335335
def unapply(x: Tree): Option[Term & x.type] = x match
336-
case x: tpd.PatternTree => None
336+
case x: (tpd.Ident & x.type) =>
337+
if x.isTerm && x.name != nme.WILDCARD then Some(x)
338+
else None
339+
case x: (tpd.Typed & x.type) =>
340+
// Matches `Typed` but not `TypedTree`
341+
TypedTypeTest.unapply(x)
337342
case x: (tpd.SeqLiteral & x.type) => Some(x)
338343
case x: (tpd.Inlined & x.type) => Some(x)
339344
case x: (tpd.NamedArg & x.type) => Some(x)
340-
case _ => if x.isTerm then Some(x) else None
345+
case x: tpd.PatternTree => None
346+
case _ =>
347+
if x.isTerm then Some(x)
348+
else None
341349
end TermTypeTest
342350

343351
object Term extends TermModule:
@@ -421,7 +429,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
421429

422430
object IdentTypeTest extends TypeTest[Tree, Ident]:
423431
def unapply(x: Tree): Option[Ident & x.type] = x match
424-
case x: (tpd.Ident & x.type) if x.isTerm => Some(x)
432+
case x: (tpd.Ident & x.type) if x.isTerm && x.name != nme.WILDCARD => Some(x)
425433
case _ => None
426434
end IdentTypeTest
427435

@@ -647,7 +655,10 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
647655

648656
object TypedTypeTest extends TypeTest[Tree, Typed]:
649657
def unapply(x: Tree): Option[Typed & x.type] = x match
650-
case x: (tpd.Typed & x.type) => Some(x)
658+
case x: (tpd.Typed & x.type) =>
659+
x.expr match
660+
case TermTypeTest(_) => Some(x)
661+
case _ => None
651662
case _ => None
652663
end TypedTypeTest
653664

@@ -1398,6 +1409,45 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
13981409
end extension
13991410
end TypeCaseDefMethods
14001411

1412+
1413+
type Wildcard = tpd.Ident
1414+
1415+
object WildcardTypeTest extends TypeTest[Tree, Wildcard]:
1416+
def unapply(x: Tree): Option[Wildcard & x.type] = x match
1417+
case x: (tpd.Ident & x.type) if x.name == nme.WILDCARD => Some(x)
1418+
case _ => None
1419+
end WildcardTypeTest
1420+
1421+
object Wildcard extends WildcardModule:
1422+
def apply(): Wildcard =
1423+
withDefaultPos(untpd.Ident(nme.WILDCARD).withType(dotc.core.Symbols.defn.AnyType))
1424+
def unapply(pattern: Wildcard): true = true
1425+
end Wildcard
1426+
1427+
type TypedTree = tpd.Typed
1428+
1429+
object TypedTreeTypeTest extends TypeTest[Tree, TypedTree]:
1430+
def unapply(x: Tree): Option[TypedTree & x.type] = x match
1431+
case x: (tpd.Typed & x.type) => Some(x)
1432+
case _ => None
1433+
end TypedTreeTypeTest
1434+
1435+
object TypedTree extends TypedTreeModule:
1436+
def apply(expr: Term, tpt: TypeTree): Typed =
1437+
withDefaultPos(tpd.Typed(xCheckMacroValidExpr(expr), tpt))
1438+
def copy(original: Tree)(expr: Term, tpt: TypeTree): Typed =
1439+
tpd.cpy.Typed(original)(xCheckMacroValidExpr(expr), tpt)
1440+
def unapply(x: Typed): (Term, TypeTree) =
1441+
(x.expr, x.tpt)
1442+
end TypedTree
1443+
1444+
given TypedTreeMethods: TypedTreeMethods with
1445+
extension (self: Typed)
1446+
def tree: Tree = self.expr
1447+
def tpt: TypeTree = self.tpt
1448+
end extension
1449+
end TypedTreeMethods
1450+
14011451
type Bind = tpd.Bind
14021452

14031453
object BindTypeTest extends TypeTest[Tree, Bind]:

compiler/src/scala/quoted/runtime/impl/printers/Extractors.scala

+4
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,16 @@ object Extractors {
165165
this += "CaseDef(" += pat += ", " += guard += ", " += body += ")"
166166
case TypeCaseDef(pat, body) =>
167167
this += "TypeCaseDef(" += pat += ", " += body += ")"
168+
case Wildcard() =>
169+
this += "Wildcard()"
168170
case Bind(name, body) =>
169171
this += "Bind(\"" += name += "\", " += body += ")"
170172
case Unapply(fun, implicits, patterns) =>
171173
this += "Unapply(" += fun += ", " ++= implicits += ", " ++= patterns += ")"
172174
case Alternatives(patterns) =>
173175
this += "Alternatives(" ++= patterns += ")"
176+
case TypedTree(tree, tpt) =>
177+
this += "TypedTree(" += tree += ", " += tpt += ")"
174178
}
175179

176180
def visitConstant(x: Constant): this.type = x match {

compiler/src/scala/quoted/runtime/impl/printers/SourceCode.scala

+20-7
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ object SourceCode {
328328
}
329329
this
330330

331-
case Ident("_") =>
331+
case Wildcard() =>
332332
this += "_"
333333

334334
case tree: Ident =>
@@ -453,6 +453,15 @@ object SourceCode {
453453
printTypeOrAnnots(tpt.tpe)
454454
}
455455
}
456+
case TypedTree(tree1, tpt) =>
457+
printPattern(tree1)
458+
tree1 match
459+
case Wildcard() =>
460+
this += ":"
461+
printType(tpt.tpe)
462+
case _ => // Alternatives, Unapply, Bind
463+
this
464+
456465

457466
case Assign(lhs, rhs) =>
458467
printTree(lhs)
@@ -896,13 +905,13 @@ object SourceCode {
896905
}
897906

898907
private def printPattern(pattern: Tree): this.type = pattern match {
899-
case Ident("_") =>
908+
case Wildcard() =>
900909
this += "_"
901910

902-
case Bind(name, Ident("_")) =>
911+
case Bind(name, Wildcard()) =>
903912
this += name
904913

905-
case Bind(name, Typed(Ident("_"), tpt)) =>
914+
case Bind(name, Typed(Wildcard(), tpt)) =>
906915
this += highlightValDef(name) += ": "
907916
printTypeTree(tpt)
908917

@@ -928,9 +937,13 @@ object SourceCode {
928937
case Alternatives(trees) =>
929938
inParens(printPatterns(trees, " | "))
930939

931-
case Typed(Ident("_"), tpt) =>
932-
this += "_: "
933-
printTypeTree(tpt)
940+
case TypedTree(tree1, tpt) =>
941+
tree1 match
942+
case Wildcard() =>
943+
this += "_: "
944+
printTypeTree(tpt)
945+
case _ =>
946+
printPattern(tree1)
934947

935948
case v: Term =>
936949
printTree(v)

library/src/scala/quoted/Quotes.scala

+71-11
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
123123
* | +- Apply
124124
* | +- TypeApply
125125
* | +- Super
126-
* | +- Typed
127126
* | +- Assign
128127
* | +- Block
129128
* | +- Closure
@@ -136,7 +135,16 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
136135
* | +- Inlined
137136
* | +- SelectOuter
138137
* | +- While
138+
* | +---+- Typed
139+
* | /
140+
* +- TypedTree +------------------·
141+
* +- Wildcard
142+
* +- Bind
143+
* +- Unapply
144+
* +- Alternatives
139145
* |
146+
* +- CaseDef
147+
* +- TypeCaseDef
140148
* |
141149
* +- TypeTree ----+- Inferred
142150
* | +- TypeIdent
@@ -154,13 +162,6 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
154162
* |
155163
* +- TypeBoundsTree
156164
* +- WildcardTypeTree
157-
* |
158-
* +- CaseDef
159-
* |
160-
* +- TypeCaseDef
161-
* +- Bind
162-
* +- Unapply
163-
* +- Alternatives
164165
*
165166
* +- ParamClause -+- TypeParamClause
166167
* +- TermParamClause
@@ -1110,8 +1111,12 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
11101111
/** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `Typed` */
11111112
given TypedTypeTest: TypeTest[Tree, Typed]
11121113

1113-
/** Tree representing a type ascription `x: T` in the source code */
1114-
type Typed <: Term
1114+
/** Tree representing a type ascription `x: T` in the source code.
1115+
*
1116+
* Also represents a pattern that contains a term `x`.
1117+
* Other `: T` patterns use the more general `TypeTree`.
1118+
*/
1119+
type Typed <: Term & TypeTree
11151120

11161121
/** Module object of `type Typed` */
11171122
val Typed: TypedModule
@@ -2039,6 +2044,56 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
20392044

20402045
// ----- Patterns ------------------------------------------------
20412046

2047+
/** Pattern representing a `_` wildcard. */
2048+
type Wildcard <: Tree
2049+
2050+
/** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `Wildcard` */
2051+
given WildcardTypeTest: TypeTest[Tree, Wildcard]
2052+
2053+
/** Module object of `type Wildcard` */
2054+
val Wildcard: WildcardModule
2055+
2056+
/** Methods of the module object `val Wildcard` */
2057+
trait WildcardModule { this: Wildcard.type =>
2058+
def apply(): Wildcard
2059+
def unapply(pattern: Wildcard): true
2060+
}
2061+
2062+
/** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `TypedTree` */
2063+
given TypedTreeTypeTest: TypeTest[Tree, TypedTree]
2064+
2065+
/** Tree representing a type ascription or pattern `x: T` in the source code
2066+
*
2067+
* The tree `x` may contain a `Constant`, `Ref`, `Wildcard`, `Bind`, `Unapply` or `Alternatives`.
2068+
*/
2069+
type TypedTree <: Term
2070+
2071+
/** Module object of `type TypedTree` */
2072+
val TypedTree: TypedTreeModule
2073+
2074+
/** Methods of the module object `val TypedTree` */
2075+
trait TypedTreeModule { this: TypedTree.type =>
2076+
2077+
/** Create a type ascription `<x: Tree>: <tpt: TypeTree>` */
2078+
def apply(expr: Tree, tpt: TypeTree): TypedTree
2079+
2080+
def copy(original: Tree)(expr: Tree, tpt: TypeTree): TypedTree
2081+
2082+
/** Matches `<expr: Tree>: <tpt: TypeTree>` */
2083+
def unapply(x: TypedTree): (Tree, TypeTree)
2084+
}
2085+
2086+
/** Makes extension methods on `TypedTree` available without any imports */
2087+
given TypedTreeMethods: TypedTreeMethods
2088+
2089+
/** Extension methods of `TypedTree` */
2090+
trait TypedTreeMethods:
2091+
extension (self: TypedTree)
2092+
def tree: Tree
2093+
def tpt: TypeTree
2094+
end extension
2095+
end TypedTreeMethods
2096+
20422097
/** Pattern representing a `_ @ _` binding. */
20432098
type Bind <: Tree
20442099

@@ -4299,9 +4354,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
42994354
case TypeBoundsTree(lo, hi) => foldTree(foldTree(x, lo)(owner), hi)(owner)
43004355
case CaseDef(pat, guard, body) => foldTree(foldTrees(foldTree(x, pat)(owner), guard)(owner), body)(owner)
43014356
case TypeCaseDef(pat, body) => foldTree(foldTree(x, pat)(owner), body)(owner)
4357+
case Wildcard() => x
43024358
case Bind(_, body) => foldTree(x, body)(owner)
43034359
case Unapply(fun, implicits, patterns) => foldTrees(foldTrees(foldTree(x, fun)(owner), implicits)(owner), patterns)(owner)
43044360
case Alternatives(patterns) => foldTrees(x, patterns)(owner)
4361+
case TypedTree(tree1, tpt) => foldTree(foldTree(x, tree1)(owner), tpt)(owner)
43054362
}
43064363
}
43074364
end TreeAccumulator
@@ -4359,12 +4416,15 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
43594416
transformCaseDef(tree)(owner)
43604417
case tree: TypeCaseDef =>
43614418
transformTypeCaseDef(tree)(owner)
4419+
case Wildcard() => tree
43624420
case pattern: Bind =>
43634421
Bind.copy(pattern)(pattern.name, pattern.pattern)
43644422
case pattern: Unapply =>
43654423
Unapply.copy(pattern)(transformTerm(pattern.fun)(owner), transformSubTrees(pattern.implicits)(owner), transformTrees(pattern.patterns)(owner))
43664424
case pattern: Alternatives =>
43674425
Alternatives.copy(pattern)(transformTrees(pattern.patterns)(owner))
4426+
case TypedTree(expr, tpt) =>
4427+
TypedTree.copy(tree)(transformTree(expr)(owner), transformTypeTree(tpt)(owner))
43684428
}
43694429
}
43704430

@@ -4415,7 +4475,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
44154475
case New(tpt) =>
44164476
New.copy(tree)(transformTypeTree(tpt)(owner))
44174477
case Typed(expr, tpt) =>
4418-
Typed.copy(tree)(/*FIXME #12222: transformTerm(expr)(owner)*/transformTree(expr)(owner).asInstanceOf[Term], transformTypeTree(tpt)(owner))
4478+
Typed.copy(tree)(transformTerm(expr)(owner), transformTypeTree(tpt)(owner))
44194479
case tree: NamedArg =>
44204480
NamedArg.copy(tree)(tree.name, transformTerm(tree.value)(owner))
44214481
case Assign(lhs, rhs) =>

project/MiMaFilters.scala

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import com.typesafe.tools.mima.core.ProblemFilters._
55
object MiMaFilters {
66
val Library: Seq[ProblemFilter] = Seq(
77
// New APIs marked @experimental in 3.0.1
8+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedTree"),
9+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedTreeMethods"),
10+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedTreeTypeTest"),
11+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule.Wildcard"),
12+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule.WildcardTypeTest"),
813
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"),
914
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMembers"),
1015
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.methodMember"),
@@ -14,6 +19,14 @@ object MiMaFilters {
1419
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TermParamClauseMethods.isErased"),
1520
exclude[MissingClassProblem]("scala.annotation.experimental"),
1621
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
22+
exclude[MissingClassProblem]("scala.quoted.Quotes$reflectModule$TypedTreeMethods"),
23+
exclude[MissingClassProblem]("scala.quoted.Quotes$reflectModule$TypedTreeModule"),
24+
exclude[MissingClassProblem]("scala.quoted.Quotes$reflectModule$WildcardModule"),
25+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedTree"),
26+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedTreeMethods"),
27+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.TypedTreeTypeTest"),
28+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.Wildcard"),
29+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule.WildcardTypeTest"),
1730
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"),
1831
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMembers"),
1932
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.methodMember"),

tests/pos-macros/i11401/X_1.scala

+9-9
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class SLSelect[S]:
1616

1717
def fold[S](s0:S)(step: (S,SLSelect[S])=> S): S = {
1818
???
19-
}
19+
}
2020

2121
def fold_async[S](s0:S)(step: (S,SLSelect[S])=> Future[S]): Future[S] = {
2222
???
@@ -27,7 +27,7 @@ class SLSelect[S]:
2727
await(s0.onRead(ch)(f).runAsync())
2828

2929
def runAsync(): Future[S] = ???
30-
30+
3131

3232

3333
object X:
@@ -36,30 +36,30 @@ object X:
3636
processImpl[T]('f)
3737
}
3838

39-
def processImpl[T:Type](t:Expr[T])(using Quotes):Expr[Future[T]] =
39+
def processImpl[T:Type](t:Expr[T])(using Quotes):Expr[Future[T]] =
4040
import quotes.reflect._
4141
val r = processTree[T](t.asTerm)
4242
r.asExprOf[Future[T]]
4343

44-
45-
def processTree[T:Type](using Quotes)(t: quotes.reflect.Term):quotes.reflect.Term =
44+
45+
def processTree[T:Type](using Quotes)(t: quotes.reflect.Term):quotes.reflect.Term =
4646
import quotes.reflect._
4747
val r: Term = t match
4848
case Inlined(_,List(),body) => processTree(body)
49-
case Inlined(d,bindings,body) =>
49+
case Inlined(d,bindings,body) =>
5050
Inlined(d,bindings,processTree[T](body))
5151
case Block(stats,expr) => Block(stats,processTree(expr))
5252
case Apply(Apply(TypeApply(Select(x,"fold"),targs),List(state)),List(fun)) =>
53-
val nFun = processLambda[T](fun)
53+
val nFun = processLambda[T](fun)
5454
Apply(Apply(TypeApply(Select.unique(x,"fold_async"),targs),List(state)),List(nFun))
5555
case Apply(TypeApply(Ident("await"),targs),List(body)) => body
5656
case Typed(x,tp) => Typed(processTree(x), Inferred(TypeRepr.of[Future].appliedTo(tp.tpe)) )
5757
case _ => throw new RuntimeException(s"tree not recoginized: $t")
5858
val checker = new TreeMap() {}
5959
checker.transformTerm(r)(Symbol.spliceOwner)
6060
r
61-
62-
def processLambda[T:Type](using Quotes)(fun: quotes.reflect.Term):quotes.reflect.Term =
61+
62+
def processLambda[T:Type](using Quotes)(fun: quotes.reflect.Term):quotes.reflect.Term =
6363
import quotes.reflect._
6464

6565
def changeArgs(oldArgs:List[Tree], newArgs:List[Tree], body:Term, owner: Symbol):Term =

0 commit comments

Comments
 (0)