scull7
scull7

Reputation: 105

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

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' }

foo(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

Upvotes: 1

Views: 201

Answers (1)

Dimitris Fasarakis Hilliard
Dimitris Fasarakis Hilliard

Reputation: 160397

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)

Upvotes: 2

Related Questions