-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Type Class Derivation Does Not Work With Contravariant Types #13146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Looks like (sadly) all of the various test variants for type class derivation all have their @@ -28,12 +28,13 @@ object Deriving {
def fromProduct(xs: EmptyTuple) = Lst.Nil
}
- implicit def LstEq[T: Eq]: Eq[Lst[T]] = Eq.derivedForSum
+ //implicit def LstEq[T: Eq]: Eq[Lst[T]] = Eq.derivedForSum[Lst[T], (Cons[T], Nil.type)]
+ implicit def LstEq[T: Eq]: Eq[Lst[T]] = Eq.derivedForSum(lstShape[T])
implicit def ConsEq[T: Eq]: Eq[Cons[T]] = Eq.derivedForProduct
implicit def NilEq[T]: Eq[Nil.type] = Eq.derivedForProduct
}
- trait Eq[T] {
+ trait Eq[-T] {
def equals(x: T, y: T): Boolean
} Otherwise you get
So, at least for that test case, it seem seems a case of widening the type bound prematurely? |
Could it be the same incremental compilation widening bug that plagues opaque types #13128 ? |
Minimized trait Eq[-T]
object Eq:
def derived[T](using m: scala.deriving.Mirror.Of[T]): Eq[T] = ???
enum Opt derives Eq:
case Nn or ...
enum Opt:
case Nn
def eq: Eq[Opt] = Eq.derived Workarround ...
enum Opt:
case Nn
def eq: Eq[Opt] = Eq.derived[Opt] |
This seems to be a limitation with type inference |
I am not sure this is a bug. |
@odersky I'm not sure what you mean when you say " Furthermore, I would argue that this reflects the natural variance of this data type. It only accepts Would love to use this feature to support derivation of functional abstractions in ZIO Prelude if we can fix this. |
@ghostbuster91 has the same problem with the I think |
That's a great point. |
In the docs here: https://docs.scala-lang.org/scala3/reference/contextual/derivation.html There's no mention of variance limitations in the documentation and I think @adamgfraser raises a great point about From @dwijnand 's comments and lack of noting limitation (due to in the documentation, this looks like an accidental oversight, not a deliberate decision, so I'm pretty disappointed to see this changed to an "enhancement" rather than the bug that it very much seems to be. |
This does not work if it is covariant either - trait TC[+T]
object TC:
def derived[T](using m: scala.deriving.Mirror.Of[T]): TC[T] = ???
enum Opt:
case N
object Opt:
def derived_TC: TC[Opt] = TC.derived
you can trace the problem to If you avoid the need for synthesis then it works: trait TC[+T]
object TC:
def derived[T](using m: scala.deriving.Mirror.Of[T]): TC[T] = ???
enum Opt:
case N
object Opt:
+ given [T]: scala.deriving.Mirror.Of[T] = ???
def derived_TC: TC[Opt] = TC.derived The problem seems to go away if we replace Edit: on first try using However this seems to be because in the recursive case scala> summon[Eq[Opt[Int]] <:< Eq[Opt.Sm[Int]]]
val res0: Eq[Opt[Int]] =:= Eq[Opt[Int]] = generalized constraint |
after a lot of experimentation I have come up with a good way to break the recursive implicit search, while being efficient with generated code: /** `P` is supplied from `Eq.derived`, it is the outer sum/product */
inline def summonAll[P, T <: Tuple]: List[Eq[_]] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => loopBreaker[P, t] :: summonAll[P, ts]
/** loopBreaker stops summoning a derived typeclass instance from inside its own definition
* @note aparently it needs to be defined separately from `summonAll` to avoid an infinite loop
* in inlining.
*/
inline def loopBreaker[P, T]: Eq[T] = compiletime.summonFrom {
case infiniteRecursion: (T =:= P) => compiletime.error("cannot derive Eq, it will cause an infinite loop")
case recursiveEvidence: (T <:< P) =>
// summonInline will work because to get here `P` must also have a Mirror instance
Eq.derived[T](using summonInline[Mirror.Of[T]])
case existing: Eq[T] => existing
} This means I can go ahead with a PR to fix the mirror synthesis |
Compiler version
3.0.1
Minimized code
Output
Expectation
This example from the documentation does not work when
Eq
is contravariant instead of invariant.The text was updated successfully, but these errors were encountered: