Here's my understanding, possibly incomplete.
Covariance in a generic type, like type Container<+T> = {}
, means: if A
is a subtype of B
, then Container<A>
is a subtype of Container<B>
. So for example, this is allowed:
type Container<+T> = {};
class Parent {};
class Child extends Parent {};
function x(a: Container<Parent>) {}
const container: Container<Child> = {};
x(container);
because Child
is a subtype of Parent
. But if you remove the +
, it's no longer allowed, and Flow gives an error on x(container)
, because container
is not a valid value of type Container<Parent>
.
And of course contravariance is the opposite: type Container<-T> = {}
means that if A
is a subtype of B
, then Container<B>
is a subtype of Container<A>
. So this is legal:
type Container<-T> = {};
class Parent {};
class Child extends Parent {};
function x(a: Container<Child>) {}
const container: Container<Parent> = {};
x(container);
Notice that my definition of Container<+T>
or Container<-T>
didn't make use of type T
at all. If it did, there would be restrictions on where it could be used, similar to how there are restrictions on what you can do with contravariant or covariant properties in an interface (i.e., they become read-only or write-only). I haven't worked out the details of these rules yet.