Skip to content

Commit

Permalink
Support Typeable for classes parameterized by wildcards (#200)
Browse files Browse the repository at this point in the history
It is sound to cast when all type arguments are wildcards.
  • Loading branch information
joroKr21 authored Jan 11, 2024
1 parent af61863 commit 4d828a0
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 4 deletions.
14 changes: 10 additions & 4 deletions modules/typeable/src/main/scala/shapeless3/typeable/typeable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ object TypeableMacros:
case _ => false
)

def isWildcard(tp: TypeRepr): Boolean = tp match
case TypeBounds(lo, hi) => lo =:= TypeRepr.of[Nothing] && hi =:= TypeRepr.of[Any]
case _ => false

def normalize(tp: TypeRepr): TypeRepr = tp match
case tp: TypeBounds => tp.low
case tp => tp
Expand Down Expand Up @@ -386,10 +390,12 @@ object TypeableMacros:
case Some(_) if sym.flags.is(Flags.Sealed) => mkSumTypeable
case _ => report.errorAndAbort(s"No Typeable for type ${target.show} with a dependent prefix")

case tp: AppliedType =>
if tp.typeSymbol.flags.is(Flags.Case) then mkCaseClassTypeable
else if tp.typeSymbol.flags.is(Flags.Sealed) then mkSumTypeable
else report.errorAndAbort(s"No Typeable for parametrized type ${target.show}")
case AppliedType(typeConstructor, args) =>
typeConstructor.classSymbol match
case Some(cls) if cls.flags.is(Flags.Case) => mkCaseClassTypeable
case Some(cls) if cls.flags.is(Flags.Sealed) => mkSumTypeable
case Some(_) if args.forall(isWildcard) => mkNamedSimpleTypeable
case _ => report.errorAndAbort(s"No Typeable for parametrized type ${target.show}")

case tp: AndType =>
summonAllTypeables(collectConjuncts(tp)) match
Expand Down
15 changes: 15 additions & 0 deletions modules/typeable/src/test/scala/shapeless3/typeable/typeable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,21 @@ class TypeableTests:
val cl2 = l.cast[Vector[?]]
assertTrue(cl2.isEmpty)

@Test
def testExistentialsClasses(): Unit =
trait T[A]
class C[A](value: A) extends T[A]
class CK[F[_]](value: F[Int]) extends T[F[Int]]

val c: Any = new C(42)
val ck: Any = new CK(Option(42))
assertTrue(c.cast[T[?]].contains(c))
assertTrue(c.cast[C[?]].contains(c))
assertTrue(ck.cast[T[?]].contains(ck))
assertTrue(ck.cast[C[?]].isEmpty)
// This wildcard is higher-kinded so we can't support it.
illTyped("Typeable[CK[?]]")

@Test
def testTraits(): Unit =
trait A
Expand Down

0 comments on commit 4d828a0

Please sign in to comment.