E234: Variance in Specialized Traits Limitation
Emitted when a type parameter of an inline trait is defined both Specialized and contravariant, to remind the user that this comes with a reduction in expressivity.
Ordinary contravariance works because of erasure, but specialized traits have a special erasure which prevents some contravariance patterns involving Object, Any, AnyVal and AnyRef.
The following example illustrates the problem:
inline trait RecyclingBin[-T: Specialized]:
def recycle(x: T) = println(s"Recycling ${x}")
def recycleAnInteger(bin: RecyclingBin[Int]) =
bin.recycle(100)
recycleAnInteger(new RecyclingBin[AnyVal]() {}) // RecyclingBin[AnyVal] can be interpreted as RecyclingBin[Int] due to contravariance
recycleAnInteger(new RecyclingBin[Any]() {}) // RecyclingBin[Any] can be interpreted as RecyclingBin[Int] due to contravariance
// Yet, this erases to:
def recycleAnInteger(bin: RecyclingBin$sp$Int) =
bin.recycle(100)
recycleAnInteger(new RecyclingBin() {}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin cannot be cast to RecyclingBin$sp$Int
recycleAnInteger(new RecyclingBin() {}.asInstanceOf[RecyclingBin$sp$Int]) // RecyclingBin cannot be cast to RecyclingBin$sp$Int
RecyclingBin[Any],RecyclingBin[AnyVal]may not be passed toRecyclingBin[Int]whereas normally they would be able to be passedRecyclingBin[Any],RecyclingBin[Object / AnyRef]may not be passed to RecyclingBin[Paper] whereas normally they would.
So we impose an additional restriction on contravariance with specialized parameters:
- If
A[F1]is to be interpreted asA[F2]underA[-T: Specialized],we require thatSpecType(F1) = SpecType(F2). Given that we also requireF1 >:> F2, and looking at the definition of SpecType this is roughly equivalent to saying F1 may not be any of the top classesAny,AnyVal,AnyRefunless F2 is also.
If this restriction is not satisfied, warning E233 is emitted, along with a type error.
In order that the creator of the contravariant specialized trait is aware of this restriction, the warning E233 is emitted at the point of definition.
Covariance has a similar problem:
- In general it works fine because it corresponds to interpreting
A[F1]asA[F2]whereF1 <:< F2. EitherA[F1]andA[F2]both erase to the same type (A$sp$SpecType(F2)orAifF1andF2are both top classes), orA[F1]erases toA$sp$F1andA[F2]erases toA. ButA$sp$F1is a subtype ofAby definition so the upcast will succeed (and upcasts are generally cheap compared to downcasts on the JVM so this is acceptable from a performance perspective). - The only exception is with
Nothing, because we want to interpretA[Nothing]as e.g.A[Int], but we eraseA[Nothing]toA.A >:> A$sp$Intso this doesn't work and we also have to ban it. This makes the code less ergonomic in some cases. For examplecase object Nil extends List[Nothing]has to becomeinline trait NilC[T: Specialized] extends List[T]andinline def Nil[T: Specialized] = new NilC[T] () {}, but we don't lose too much expressivity.
Example
inline trait Foo[-T: Specialized] // warning: Type parameter is both Specialized and contravariant.
Error
-- [E234] Potential Issue Warning: example.scala:1:18 --------------------------
1 |inline trait Foo[-T: Specialized] // warning: Type parameter is both Specialized and contravariant.
| ^
|Type parameter is both Specialized and variant. This imposes additional typing restrictions.
|-----------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Specialized traits achieve a performance gain through a special erasure.
| - Primitives are specialized: Foo[Int] erases to Foo$sp$Int
| - Reference types are specialized to the highest non-top class: Foo[Lion] erases to Foo$sp$Animal
| - Top classes are erased normally: Foo[Any] / Foo[AnyVal] / Foo[Object] / Foo[AnyRef] erase to Foo.
| This means that certain variance patterns that cross these erasure categories will fail at
| runtime due to a ClassCastException, so they are not permitted.
|
| For example, treating Foo[Any] as Foo[Animal] via contravariance is not allowed with Specialized.
|
| Please see the docs for more information on how specialized traits are erased.
|
| If you accept this limitation you can silence this warning with @nowarn. For example:
|
| @nowarn("id=E234")
| inline trait Foo[-T: Specialized]:
|
| Otherwise, remove Specialized, or remove the variance.
|
-----------------------------------------------------------------------------
Solution
Accept downsides and ignore warning as follows, or remove one of Specialized / variant.
import scala.annotation.nowarn
@nowarn("id=E234")
inline trait Foo[-T: Specialized]