Skip to content

Commit bd05765

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(_)`. 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(_)` Fixes #12188
1 parent 2ee311e commit bd05765

File tree

13 files changed

+107
-19
lines changed

13 files changed

+107
-19
lines changed

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
421421

422422
object IdentTypeTest extends TypeTest[Tree, Ident]:
423423
def unapply(x: Tree): Option[Ident & x.type] = x match
424-
case x: (tpd.Ident & x.type) if x.isTerm => Some(x)
424+
case x: (tpd.Ident & x.type) if x.isTerm && x.name != nme.WILDCARD => Some(x)
425425
case _ => None
426426
end IdentTypeTest
427427

@@ -1398,6 +1398,21 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
13981398
end extension
13991399
end TypeCaseDefMethods
14001400

1401+
1402+
type Wildcard = tpd.Ident
1403+
1404+
object WildcardTypeTest extends TypeTest[Tree, Wildcard]:
1405+
def unapply(x: Tree): Option[Wildcard & x.type] = x match
1406+
case x: (tpd.Ident & x.type) if x.name == nme.WILDCARD => Some(x)
1407+
case _ => None
1408+
end WildcardTypeTest
1409+
1410+
object Wildcard extends WildcardModule:
1411+
def apply(): Wildcard =
1412+
withDefaultPos(untpd.Ident(nme.WILDCARD).withType(dotc.core.Symbols.defn.AnyType))
1413+
def unapply(pattern: Wildcard): true = true
1414+
end Wildcard
1415+
14011416
type Bind = tpd.Bind
14021417

14031418
object BindTypeTest extends TypeTest[Tree, Bind]:

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ 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) =>

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

Lines changed: 5 additions & 5 deletions
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 =>
@@ -896,13 +896,13 @@ object SourceCode {
896896
}
897897

898898
private def printPattern(pattern: Tree): this.type = pattern match {
899-
case Ident("_") =>
899+
case Wildcard() =>
900900
this += "_"
901901

902-
case Bind(name, Ident("_")) =>
902+
case Bind(name, Wildcard()) =>
903903
this += name
904904

905-
case Bind(name, Typed(Ident("_"), tpt)) =>
905+
case Bind(name, Typed(Wildcard(), tpt)) =>
906906
this += highlightValDef(name) += ": "
907907
printTypeTree(tpt)
908908

@@ -928,7 +928,7 @@ object SourceCode {
928928
case Alternatives(trees) =>
929929
inParens(printPatterns(trees, " | "))
930930

931-
case Typed(Ident("_"), tpt) =>
931+
case Typed(Wildcard(), tpt) =>
932932
this += "_: "
933933
printTypeTree(tpt)
934934

library/src/scala/quoted/Quotes.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
158158
* +- CaseDef
159159
* |
160160
* +- TypeCaseDef
161+
* +- Wildcard
161162
* +- Bind
162163
* +- Unapply
163164
* +- Alternatives
@@ -2039,6 +2040,21 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
20392040

20402041
// ----- Patterns ------------------------------------------------
20412042

2043+
/** Pattern representing a `_` wildcard. */
2044+
type Wildcard <: Tree
2045+
2046+
/** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `Wildcard` */
2047+
given WildcardTypeTest: TypeTest[Tree, Wildcard]
2048+
2049+
/** Module object of `type Wildcard` */
2050+
val Wildcard: WildcardModule
2051+
2052+
/** Methods of the module object `val Wildcard` */
2053+
trait WildcardModule { this: Wildcard.type =>
2054+
def apply(): Wildcard
2055+
def unapply(pattern: Wildcard): true
2056+
}
2057+
20422058
/** Pattern representing a `_ @ _` binding. */
20432059
type Bind <: Tree
20442060

@@ -4263,6 +4279,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
42634279
case TypeBoundsTree(lo, hi) => foldTree(foldTree(x, lo)(owner), hi)(owner)
42644280
case CaseDef(pat, guard, body) => foldTree(foldTrees(foldTree(x, pat)(owner), guard)(owner), body)(owner)
42654281
case TypeCaseDef(pat, body) => foldTree(foldTree(x, pat)(owner), body)(owner)
4282+
case Wildcard() => x
42664283
case Bind(_, body) => foldTree(x, body)(owner)
42674284
case Unapply(fun, implicits, patterns) => foldTrees(foldTrees(foldTree(x, fun)(owner), implicits)(owner), patterns)(owner)
42684285
case Alternatives(patterns) => foldTrees(x, patterns)(owner)
@@ -4323,6 +4340,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
43234340
transformCaseDef(tree)(owner)
43244341
case tree: TypeCaseDef =>
43254342
transformTypeCaseDef(tree)(owner)
4343+
case Wildcard() => tree
43264344
case pattern: Bind =>
43274345
Bind.copy(pattern)(pattern.name, pattern.pattern)
43284346
case pattern: Unapply =>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import scala.quoted.*
2+
3+
object MatchTest {
4+
inline def test[T](inline obj: Any): Unit = ${testImpl('obj)}
5+
6+
def testImpl[T](objExpr: Expr[T])(using Quotes): Expr[Unit] = {
7+
import quotes.reflect.*
8+
// test that the extractors work
9+
val Inlined(None, Nil, Block(Nil, Match(param @ Ident("a"), List(CaseDef(Literal(IntConstant(1)), None, Block(Nil, Literal(UnitConstant()))), CaseDef(Wildcard(), None, Block(Nil, Literal(UnitConstant()))))))) = objExpr.asTerm
10+
// test that the constructors work
11+
Block(Nil, Match(param, List(CaseDef(Literal(IntConstant(1)), None, Block(Nil, Literal(UnitConstant()))), CaseDef(Wildcard(), None, Block(Nil, Literal(UnitConstant())))))).asExprOf[Unit]
12+
}
13+
}

tests/pos-macros/i12188b/Test_2.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
def test(a: Int) = MatchTest.test {
3+
a match
4+
case 1 =>
5+
case _ =>
6+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
DefDef("foo", Nil, TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(1)), "+"), List(Literal(IntConstant(2))))))
22
ValDef("bar", TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(2)), "+"), List(Literal(IntConstant(3))))))
3-
Bind("x", Ident("_"))
3+
Bind("x", Wildcard())
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
DefDef("foo", Nil, TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(1)), "+"), List(Literal(IntConstant(2))))))
22
ValDef("bar", TypeIdent("Int"), Some(Apply(Select(Literal(IntConstant(2)), "+"), List(Literal(IntConstant(3))))))
3-
Bind("x", Ident("_"))
3+
Bind("x", Wildcard())

tests/run-macros/i12188.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PC1
2+
PC2
3+
default

tests/run-macros/i12188/Macro_1.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import scala.quoted.*
2+
3+
object MatchTest {
4+
inline def test[T](inline obj: T): String = ${testImpl('obj)}
5+
6+
def testImpl[T](objExpr: Expr[T])(using qctx: Quotes, t: Type[T]): Expr[String] = {
7+
import qctx.reflect.*
8+
9+
val obj = objExpr.asTerm
10+
val cases = obj.tpe.typeSymbol.children.map { child =>
11+
val subtype = TypeIdent(child)
12+
val bind = Symbol.newBind(Symbol.spliceOwner, "c", Flags.EmptyFlags, subtype.tpe)
13+
CaseDef(Bind(bind, Typed(Ref(bind), subtype)), None, Literal(StringConstant(subtype.show)))
14+
} ::: {
15+
CaseDef(Wildcard(), None, Literal(StringConstant("default")))
16+
} :: Nil
17+
val bind = Symbol.newBind(Symbol.spliceOwner, "o", Flags.EmptyFlags, obj.tpe)
18+
val result = Match(obj, cases)
19+
val code = result.show(using Printer.TreeAnsiCode)
20+
// println(code)
21+
result.asExprOf[String]
22+
}
23+
}

0 commit comments

Comments
 (0)