Why doesn't Flow understand that nullable properties are compatible with non-nullable object properties?

scull7 Source

I'm trying to understand why flow is complaining about incompatibility between the properties of two very similar objects (try-flow-example):

/* @flow */
type Cow = {
  name: ?string

type NamedCow = {
  name: string

function foo (c: Cow): string {
  if (c.name == null) return 'anonymous'
  return c.name

const elsa: NamedCow = { name: 'Elsa' }


Flow gives the following error with the above code:

17: foo(elsa)
        ^ NamedCow. This type is incompatible with the expected param type of
10: function foo (c: Cow): string {
                     ^ Cow
    Property `name` is incompatible:
        7:   name: string
                   ^ string. This type is incompatible with
        3:   name: ?string
                   ^ null or undefined

Why is the more specific name: string incompatible with the less specific name: ?string? Wouldn't NamedCow be covariant to Cow over the name property since the name property of NamedCow is a sub-set of the name property of Cow?

Relevant docs: Flow docs on subtypes



answered 5 days ago Jim Fasarakis Hilliard #1

Object types are invariant. This is due to the fact that object properties can be read and written.

To see why covariance is unsafe, consider a function that assigns null to the property name in the foo function.

Passing a Cow type: assignment to name = null poses no issue since name can be ?string.

But, if you pass a NamedCow type, the assignment to name = null would violate the type for name which is only string.

You can annotate name to be covariant by prefixing it with +, that is +name: ?string. This indicates that no writes will be performed on it. That should clear away the errors you're currently seeing. (The property variance modifiers are somewhat hidden in the docs)

comments powered by Disqus