Skip to content

Type holes in protected[this] abstract types #370

Closed
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions