Skip to main content

Depth Subtyping

Assume we have two classes, which have a subtype relationship using extends:

1class Person {2  name: string;3}4class Employee extends Person {5  department: string;6}

It's valid to use an Employee instance where a Person instance is expected.

1class Person { name: string }2class Employee extends Person { department: string }3
4const employee: Employee = new Employee();5const person: Person = employee; // OK

However, it is not valid to use an object containing an Employee instance where an object containing a Person instance is expected.

1class Person { name: string }2class Employee extends Person { department: string }3
4const employee: {who: Employee} = {who: new Employee()};5const person: {who: Person} = employee; // Error
5:31-5:38: Cannot assign `employee` to `person` because `Person` [1] is incompatible with `Employee` [2] in property `who`. This property is invariantly typed. See https://flow.org/en/docs/faq/#why-cant-i-pass-a-string-to-a-function-that-takes-a-string-number. [incompatible-type]

This is an error because objects are mutable. The value referenced by the employee variable is the same as the value referenced by the person variable.

person.who = new Person();

If we write into the who property of the person object, we've also changed the value of employee.who, which is explicitly annotated to be an Employee instance.

If we prevented any code from ever writing a new value to the object through the person variable, it would be safe to use the employee variable. Flow provides a syntax for this:

1class Person { name: string }2class Employee extends Person { department: string }3
4const employee: {who: Employee} = {who: new Employee()};5const person: {+who: Person} = employee; // OK6person.who = new Person(); // Error!
6:8-6:10: Cannot assign `new Person()` to `person.who` because property `who` is not writable. [cannot-write]

The plus sign + indicates that the who property is covariant. Using a covariant property allows us to use objects which have subtype-compatible values for that property. By default, object properties are invariant, which allow both reads and writes, but are more restrictive in the values they accept.

Read more about property variance.