Skip to content

Commit 33a8c93

Browse files
committed
Fix Reflection wildcard abstraction
Currently we are missing in the API the Wilcard concept for `case _ =>` patterns. These are encoded as term `Ident` with name `_`. Currently this tree can be inconsitently matched by `Ident` or `WildcardTypeTree`. There is also no way to create an `Ident(_)`. `TypeIdent` is the only one that works as expected. Changes * `Ident` 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 32e725f commit 33a8c93

File tree

9 files changed

+92
-3
lines changed

9 files changed

+92
-3
lines changed

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
422422

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

@@ -1021,7 +1021,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
10211021

10221022
object TypeIdentTypeTest extends TypeTest[Tree, TypeIdent]:
10231023
def unapply(x: Tree): Option[TypeIdent & x.type] = x match
1024-
case tpt: (tpd.Ident & x.type) if tpt.isType => Some(tpt)
1024+
case tpt: (tpd.Ident & x.type) if tpt.isType && tpt.name != nme.WILDCARD => Some(tpt)
10251025
case _ => None
10261026
end TypeIdentTypeTest
10271027

@@ -1335,7 +1335,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
13351335

13361336
object WildcardTypeTreeTypeTest extends TypeTest[Tree, WildcardTypeTree]:
13371337
def unapply(x: Tree): Option[WildcardTypeTree & x.type] = x match
1338-
case x: (tpd.Ident & x.type) if x.name == nme.WILDCARD => Some(x)
1338+
case x: (tpd.Ident & x.type) if x.isType && x.name == nme.WILDCARD => Some(x)
13391339
case _ => None
13401340
end WildcardTypeTreeTypeTest
13411341

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

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

14041419
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ object SourceCode {
530530
case Closure(meth, _) =>
531531
printTree(meth)
532532

533+
case Wildcard() =>
534+
this += "_"
535+
533536
case _ =>
534537
throw new MatchError(tree.show(using Printer.TreeStructure))
535538

library/src/scala/quoted/Quotes.scala

Lines changed: 16 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

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+
}

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+
}

tests/run-macros/i12188/Test_2.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
sealed trait P
2+
case class PC1(a: String) extends P
3+
case class PC2(b: Int) extends P
4+
5+
@main def Test =
6+
println(MatchTest.test(PC1("ab"): P))
7+
println(MatchTest.test(PC2(10): P))
8+
println(MatchTest.test(null: P))

0 commit comments

Comments
 (0)