Assuming Covariance for generics is unsound.
astrothayne@gmail.com
Submitted byLink to original bug (#796291)
Description
Given class A inherits from class B. And there is a generic class C, then vala's type system assumes that C<A>
is a subclass of C<B>
(C is covariant). However if C is contravariant the correct relationship should be that C<B>
is a subclass of C<A>
, and if C is invariant, then there is no relationship between C<A>
and C<B>
. See https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science).
If C<T>
has a method that takes a parameter of type T
, then if that method is called on a C<A>
(runtime) as a C<B>
(compile-time), then an object that isn't of type A
might end up being used as an A
.
As a concrete example:
class Parent: Object {
public int a;
public Parent(int a) {
this.a = a;
}
}
class Child: Parent {
public int b;
public Child(int a) {
base(a);
this.b = a+1;
}
public void foo() {
stdout.printf(@"b = $b\n");
}
public static int main(string[] args) {
var list = new List<Child>();
list.append(new Child(1));
test(list);
list.foreach((entry) => {
stdout.printf(@"entry $(entry.a)\n");
entry.foo(); // oops!
});
return 0;
}
}
void test(List<Parent> list) {
list.append(new Parent(5));
}
When foo
is called on the second entry of the list, it will print garbage (or maybe segfault), because the Parent
class doesn't have a b
field.
Maybe there is a good reason for this, and I don't know of a great way to remedy it without breaking backwards compatibility. But I haven't seen any comments about this issue in the documentation, and at the very list there should be a clear warning in the documentation for generics about the danger of assuming covariance.
Version: 0.36.x