Flow: "const abc: string = {}.foo;" is wrong but passes type-checking.

Created on 20 Oct 2016  路  3Comments  路  Source: facebook/flow

(Tested with master, 0.33.0 and 0.29.0, via the REPL.)

A longer example:

/* @flow */
"use strict";

const abc: string = {}.foo;
const def: string = abc.concat("... is undefined"); // explodes at run-time
console.log(def);

(You can sub in the required type of abc with number or a record structure, and it'll still happily accept the undefined result.)

object model unsealed objects

Most helpful comment

Yeah, this is a known issue and the cause is somewhat complicated.

Flow supports a common pattern in JS where objects are built up incrementally:

var o = {};
write(o);
read(o);

function write(o) {
  o.foo = 0;
}

function read(o) {
  (o.foo: number);
}

The above example shows how properties can be written and read far away from the initial variable declaration. To support this, Flow considers the object unsealed. When in this mode, Flow allows any read of a property, as long as it's compatible with the corresponding write.

However, if there is no corresponding write, there is nothing for the read to be incompatible with.

The solution here is to recognize when an unsealed object has properties that are read from, but never written to. Due to the design of Flow itself, there's no good place to insert this check currently. Eventually, the plan is to "write" an empty type into any property that has no writes. empty is incompatible with all uses, so we will emit an error.

I am working on a few changes that should expose the opportunity to add this check, so assigning myself here. Will update this issue when the fix lands.

All 3 comments

Yeah, this is a known issue and the cause is somewhat complicated.

Flow supports a common pattern in JS where objects are built up incrementally:

var o = {};
write(o);
read(o);

function write(o) {
  o.foo = 0;
}

function read(o) {
  (o.foo: number);
}

The above example shows how properties can be written and read far away from the initial variable declaration. To support this, Flow considers the object unsealed. When in this mode, Flow allows any read of a property, as long as it's compatible with the corresponding write.

However, if there is no corresponding write, there is nothing for the read to be incompatible with.

The solution here is to recognize when an unsealed object has properties that are read from, but never written to. Due to the design of Flow itself, there's no good place to insert this check currently. Eventually, the plan is to "write" an empty type into any property that has no writes. empty is incompatible with all uses, so we will emit an error.

I am working on a few changes that should expose the opportunity to add this check, so assigning myself here. Will update this issue when the fix lands.

Thanks for the explanation. 馃槉

Could this be covered with a lint rule that disallows the following 2 cases:

  1. Bindings for untyped identifiers cannot be bound to a value that is an empty ObjectExpression.
const obj = {}; // Illegal
  1. MemberExpression cannot have an empty ObjectExpression as it's object.
{}.foo; // illegal
Was this page helpful?
0 / 5 - 0 ratings