Skip to content

Type holes in protected[this] abstract types #370

@acrylic-origami

Description

@acrylic-origami

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions