Closed
Description
Variant types can take invariant positions in object-protected (protected[this]
) abstract type, but these abstract types can also be exposed publicly [sometimes*]. For example, both of the following are legal, although they shouldn't be:
// 1. Type bound with +T in invariant position, then exposed publicly:
abstract class InvariantBoundAbstractType[+T] {
protected[this] type TSeq <: MutableList[T]
var v: TSeq
}
// 2. Locally, the type is bound properly, but not for subclass:
abstract class VariantBoundAbstractType[+T] {
protected[this] type TSeq <: Seq[T]
protected[this] val v: TSeq
def get(): TSeq = v
// None of this is immediately erroneous,
// but can be if subclassed as below:
}
class InvariantConcreteType[+T](protected[this] val v: MutableList[T]) extends VariantBoundAbstractType[T] {
protected[this] type TSeq = MutableList[T] // no complaints for this assignment
}
The violations are classic:
// 1.
val wrapper = (new InvariantBoundAbstractType[Int] {
type TSeq = MutableList[Int]
var v = MutableList(42)
})
(wrapper: InvariantBoundAbstractType[Any]).v += "string!"
wrapper.v.last + 42
// java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
//2.
var A = MutableList(42)
(new InvariantConcreteType[Int](A): VariantBoundAbstractType[Any]).get() += "string!"
A.last + 42
// java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
*The typechecker catches this hole occasionally when the violation is simpler. For example, the following fails correctly:
class DirectInvariantConcreteType[+T] {
protected[this] type TSeq = MutableList[T] // assignment makes set() a no-go, whereas lower-bounding would be fine
def set(v: TSeq): Unit // "covariant type T occurs in invariant position"
}
Metadata
Metadata
Assignees
Labels
No labels