Skip to content

Commit 8e4dfc1

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 scala#12188
1 parent 1abf186 commit 8e4dfc1

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
@@ -420,7 +420,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
420420

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

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

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

14021417
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
@@ -157,6 +157,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
157157
* +- CaseDef
158158
* |
159159
* +- TypeCaseDef
160+
* +- Wildcard
160161
* +- Bind
161162
* +- Unapply
162163
* +- Alternatives
@@ -2020,6 +2021,21 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
20202021

20212022
// ----- Patterns ------------------------------------------------
20222023

2024+
/** Pattern representing a `_` wildcard. */
2025+
type Wildcard <: Tree
2026+
2027+
/** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `Wildcard` */
2028+
given WildcardTypeTest: TypeTest[Tree, Wildcard]
2029+
2030+
/** Module object of `type Wildcard` */
2031+
val Wildcard: WildcardModule
2032+
2033+
/** Methods of the module object `val Wildcard` */
2034+
trait WildcardModule { this: Wildcard.type =>
2035+
def apply(): Wildcard
2036+
def unapply(pattern: Wildcard): true
2037+
}
2038+
20232039
/** Pattern representing a `_ @ _` binding. */
20242040
type Bind <: Tree
20252041

@@ -4243,6 +4259,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
42434259
case TypeBoundsTree(lo, hi) => foldTree(foldTree(x, lo)(owner), hi)(owner)
42444260
case CaseDef(pat, guard, body) => foldTree(foldTrees(foldTree(x, pat)(owner), guard)(owner), body)(owner)
42454261
case TypeCaseDef(pat, body) => foldTree(foldTree(x, pat)(owner), body)(owner)
4262+
case Wildcard() => x
42464263
case Bind(_, body) => foldTree(x, body)(owner)
42474264
case Unapply(fun, implicits, patterns) => foldTrees(foldTrees(foldTree(x, fun)(owner), implicits)(owner), patterns)(owner)
42484265
case Alternatives(patterns) => foldTrees(x, patterns)(owner)
@@ -4303,6 +4320,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
43034320
transformCaseDef(tree)(owner)
43044321
case tree: TypeCaseDef =>
43054322
transformTypeCaseDef(tree)(owner)
4323+
case Wildcard() => tree
43064324
case pattern: Bind =>
43074325
Bind.copy(pattern)(pattern.name, pattern.pattern)
43084326
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)