Flow: Property cannot be accessed on possibly null value even when protected by if statement

Created on 6 Dec 2017  路  2Comments  路  Source: facebook/flow

Reproducible in the latest version (0.60.1): Try Flow

Consider these types:

type Prefs = {
  favorite_color: string,
  favorite_number: string,
};

type Person = {
  prefs: Prefs | null,
};

const person: Person = { prefs: null };

Given these types, Flow does not understand that subsequent lines are still protected by an if statement. It only understands that the first line is protected. Example:

if (person.prefs) {
  // This does not cause an error.
  console.log(person.prefs.favorite_color);
  // This causes an error (but should not).
  console.log(person.prefs.favorite_number);
}

Flow reports this error which is incorrect since the code is protected by an if statement:

17:   console.log(person.prefs.favorite_number);
                               ^ property `favorite_number`. Property cannot be accessed on possibly null value
17:   console.log(person.prefs.favorite_number);
                  ^ null

Most helpful comment

This is correct because getters in Javascript can have side effects which would invalidate the type check. You can actually break Flow's ability to infer when things are nullable with a getter:

class P {
  _prefs: null|Prefs;

  get prefs() : Prefs | null {
    const old = this._prefs;
    this._prefs = null;
    return old;
  }
}

For this reason, Flow doesn't allow get()/set() methods by default. But an instance of class P here would satisfy your type Person and would completely break your code.

Basically what you want to do, to be safe from side effects (and satisfy Flow), is this:

const prefs = person.prefs;
if(prefs) {

};

If you have to access a shared mutable variable, copy the reference to it always because Javascript is such a fundamentally unsafe language. If you don't know that accessing this.foo.bar doesn't have side effects (and you can't, because even if you know it's not gated behind a get function, it can still be proxied and that's not determinable even at runtime), then there's no way to safely use the pattern you presented here.

All 2 comments

Assigning person.prefs to a variable seems to be a viable workaround.

const person: Person = { prefs: null };
const prefs = person.prefs;
if (prefs) {
  // ...
}

This is correct because getters in Javascript can have side effects which would invalidate the type check. You can actually break Flow's ability to infer when things are nullable with a getter:

class P {
  _prefs: null|Prefs;

  get prefs() : Prefs | null {
    const old = this._prefs;
    this._prefs = null;
    return old;
  }
}

For this reason, Flow doesn't allow get()/set() methods by default. But an instance of class P here would satisfy your type Person and would completely break your code.

Basically what you want to do, to be safe from side effects (and satisfy Flow), is this:

const prefs = person.prefs;
if(prefs) {

};

If you have to access a shared mutable variable, copy the reference to it always because Javascript is such a fundamentally unsafe language. If you don't know that accessing this.foo.bar doesn't have side effects (and you can't, because even if you know it's not gated behind a get function, it can still be proxied and that's not determinable even at runtime), then there's no way to safely use the pattern you presented here.

Was this page helpful?
0 / 5 - 0 ratings