typelevel / kittens

Automatic type class derivation for Cats
Apache License 2.0
533 stars 64 forks source link

Typeclass derivation doesn't work for type literals #568

Closed keuhdall closed 10 months ago

keuhdall commented 1 year ago

Hi, I've been trying to use kittens to derive Show on one of my case class, however it seems to fail because of the type literals.

My case class looks like this:

case class BearerToken(
    access_token: String,
    token_type: "bearer",
    expires_in: Long,
    refresh_token: String,
    scope: "public",
    created_at: Long
) derives Show

When compiling, I get the following error message:

[error]    |No given instance of type cats.derived.DerivedShow[types.BearerToken] was found.
[error]    |I found:
[error]    |
[error]    |    cats.derived.DerivedShow.given_DerivedShow_A[types.BearerToken](
[error]    |      {
[error]    |        val gen$proxy1:
[error]    |
[error]    |            deriving.Mirror.Product{
[error]    |              type Kind = shapeless3.deriving².K0.type;
[error]    |                type MirroredType = types.BearerToken;
[error]    |                type MirroredMonoType = types.BearerToken;
[error]    |                type MirroredElemTypes <: Tuple
[error]    |            }
[error]    |           &
[error]    |            scala.deriving.Mirror.Product{
[error]    |              type MirroredMonoType = types.BearerToken;
[error]    |                type MirroredType = types.BearerToken;
[error]    |                type MirroredLabel = ("BearerToken" : String);
[error]    |                type MirroredElemTypes = (String, ("bearer" : String), Long,
[error]    |                  String, ("public" : String), Long);
[error]    |                type MirroredElemLabels = (("access_token" : String),
[error]    |                  ("token_type" : String), ("expires_in" : String),
[error]    |                  ("refresh_token" : String), ("scope" : String),
[error]    |                  ("created_at" : String))
[error]    |            }
[error]    |
[error]    |         =
[error]    |          types.BearerToken.$asInstanceOf[
[error]    |
[error]    |              deriving.Mirror.Product{
[error]    |                type Kind = shapeless3.deriving².K0.type;
[error]    |                  type MirroredType = types.BearerToken;
[error]    |                  type MirroredMonoType = types.BearerToken;
[error]    |                  type MirroredElemTypes <: Tuple
[error]    |              }
[error]    |             &
[error]    |              scala.deriving.Mirror.Product{
[error]    |                type MirroredMonoType = types.BearerToken;
[error]    |                  type MirroredType = types.BearerToken;
[error]    |                  type MirroredLabel = ("BearerToken" : String);
[error]    |                  type MirroredElemTypes = (String, ("bearer" : String), Long,
[error]    |                    String, ("public" : String), Long);
[error]    |                  type MirroredElemLabels = (("access_token" : String),
[error]    |                    ("token_type" : String), ("expires_in" : String),
[error]    |                    ("refresh_token" : String), ("scope" : String),
[error]    |                    ("created_at" : String))
[error]    |              }
[error]    |
[error]    |          ]
[error]    |        shapeless3.deriving².internals.ErasedProductInstancesN.apply[
[error]    |          shapeless3.deriving².K0.type,
[error]    |          cats.derived.Derived.Or[cats.Show[types.BearerToken]]](gen$proxy1,
[error]    |          {
[error]    |            val arr$proxy1: Array[Any] = new Array[Any](6)
[error]    |            {
[error]    |              arr$proxy1.update(0,
[error]    |                {
[error]    |                  given val instance: cats.Show[String] =
[error]    |                    cats.implicits.catsStdShowForString
[error]    |                  cats.derived.Derived.Or.apply²[cats.Show[String]](instance)
[error]    |                }:cats.derived.Derived.Or[cats.Show[String]]
[error]    |              )
[error]    |              {
[error]    |                arr$proxy1.update(1,
[error]    |                  compiletime.summonInline[
[error]    |                    cats.derived.Derived.Or[cats.Show[("bearer" : String)]]]
[error]    |                )
[error]    |                shapeless3.deriving².summonAsArray0[
[error]    |                  (cats.derived.Derived.Or[cats.Show[Long]],
[error]    |                    cats.derived.Derived.Or[cats.Show[String]],
[error]    |                    cats.derived.Derived.Or[cats.Show[("public" : String)]],
[error]    |                    cats.derived.Derived.Or[cats.Show[Long]])
[error]    |                ](2, arr$proxy1)
[error]    |              }:Array[Any]
[error]    |            }:Array[Any]
[error]    |          }:Array[Any]
[error]    |        ):
[error]    |          shapeless3.deriving².internals.ErasedProductInstances[
[error]    |            shapeless3.deriving².K0.type,
[error]    |            cats.derived.Derived.Or[cats.Show[types.BearerToken]]]
[error]    |        :
[error]    |          shapeless3.deriving².K0.ProductInstances[
[error]    |            [A] =>> cats.derived.Derived.Or[cats.Show[A]], types.BearerToken]
[error]    |      },
[error]    |    ???)
[error]    |
[error]    |But given instance mkProductInstances in object K0 does not match type shapeless3.deriving².K0.ProductInstances[cats.derived.DerivedShow.Or²,
[error]    |  types.BearerToken]
[error]    |
[error]    |where:    Or        is a type in object Derived with bounds <: [A] =>> Any
[error]    |          Or²       is a type in object DerivedShow which is an alias of [A] =>> cats.derived.Derived.Or[cats.Show[A]]
[error]    |          apply     is a method in object ErasedProductInstancesN
[error]    |          apply²    is a method in object Or
[error]    |          deriving  is a package in package scala
[error]    |          deriving² is a package in package shapeless3
[error]    |.
[error]    |----------------------------------------------------------------------------
[error]    |Inline stack trace
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |This location contains code that was inlined from DerivedShow.scala:20
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |This location contains code that was inlined from DerivedShow.scala:20
[error]     ----------------------------------------------------------------------------

However, when switching types of token_type and scope back to String, everything works just fine.

I'm using kittens 3.0.0 and Scala 3.3.0

joroKr21 commented 1 year ago

Hi, it looks like there was an oversight while implementing Show - we should be using ContravariantShow. In the meantime you can work around this problem by defining given [A <: String]: Show[A] = _.toString.

keuhdall commented 1 year ago

The fix works indeed, thanks ! Out of curiosity I tried deriving Eq, and I get the same issue, so it affects probably other typeclasses too.

joroKr21 commented 1 year ago

Out of curiosity I tried deriving Eq, and I get the same issue, so it affects probably other typeclasses too.

True, but there is no ContravariantEq typeclass. Now that I thought of it some more maybe this should be fixed in Cats proper by adding those instances 🤔 - about Eq it could even just return true for singleton types.

keuhdall commented 1 year ago

Sorry for the late reply, should I open an issue on Cats related to this one ?

joroKr21 commented 1 year ago

Sorry for the late reply, should I open an issue on Cats related to this one ?

That would be helpful, yes 👍

keuhdall commented 1 year ago

Will do ! I took some time to test most of the typeclasses supported by kittens (I used the same case class as above for my tests), here's a recap:

Typeclass Status
Eq
PartialOrder
Order
Hash
Show
Semigroup
CommutativeSemigroup
Monoid
CommutativeMonoid
Functor
Contravariant
Invariant
Pure *
Apply
Applicative
Foldable
Reducible
Traverse
Empty *
NonEmptyTraverse
joroKr21 commented 11 months ago

Here's my take on it:

Typeclass Status
Eq We can probably implement it here
PartialOrder We can probably implement it here
Order We can probably implement it here
Hash Theoretically it should work, but it's tricky
Show We can use ContravariantShow
Semigroup Can't work - how do you combine singletons?
CommutativeSemigroup Same as Semigroup
Monoid Same as Semigroup
CommutativeMonoid Same as Semigroup
Functor
Contravariant
Invariant
Pure * We can probably make it work
Apply Can't work because no Semigroup
Applicative Can't work because no Semigroup
Foldable
Reducible That's surprising, it should work
Traverse
Empty * I don't think we should allow this
NonEmptyTraverse That's surprising, it should work
joroKr21 commented 10 months ago

@keuhdall I've implemented my suggestion - lmk what you think