Flow: $ObjMapi, $ObjMap and $TupleMap allow invalid writes

Created on 23 Oct 2016  路  8Comments  路  Source: facebook/flow

Example 1:

type A = $ObjMapi<{ FOO: null }, <K>(k:K) => K>
declare var a: A;

(a.FOO : 'FOO'); // ok
(a.FOO : 'BAR'); // error
a.FOO = 'BAR'; // no error

Example 2:

type A = $ObjMap<{ FOO: null }, <K>(k:K) => 'FOO'>
declare var a: A;

(a.FOO : 'FOO'); // ok
(a.FOO : 'BAR'); // error
a.FOO = 'BAR'; // no error

Example 3:

type A = $TupleMap<[mixed, mixed], <K>(k:K) => 'FOO'>
declare var a: A;

(a[0] : 'FOO'); // ok
(a[0]: 'BAR'); // error
a[0] = 'BAR'; // no error

See: https://github.com/gcanti/flow-runtime/issues/11

/cc @gcanti

object model incompleteness

Most helpful comment

Hey, just wanted to see if there were any plans for this. Would be pretty cool to have this working properly!

All 8 comments

This is pretty bad for libraries like validated or flow-io which rely on $ObjMap in order to build and extract a static type from a runtime value representing a schema.

/cc @andreypopp

This affects only setting properties and literal assignment:

var a: A = { FOO: 'BAR' }; // no error, but should be

but:

declare var b: { FOO: 'BAR' };
var a: A = b; // an error, as expected

and:

type A = $ObjMap<{ FOO: null }, <K>(k:K) => 'FOO'>

declare var b: { FOO: number };
var a: A = b; // an error, as expected

but:

declare var b: { FOO: string };
var a: A = b; // no error, but should be

You're right, this is a problem. The cause in the implementation is that we use the existing mechanism for function calls to perform the mapping. This is convenient, but inherits an unfortunate behavior:

declare function f(): string;
var x = f();
if (true) x = 0; // cond. assign to avoid definite assignment refinement
(x: void); // errors: number|string ~> void

Calling a function returns a new, open tvar with the function return type as a lower bound. This doesn't prevent more lower bounds from flowing in, but all inflowing lower bounds are checked against any uses, so it's not totally unsound.

Just explaining this behavior, not excusing it. We should work to make the generate type more precise, because unlike function calls, these types come from annotations where people rightfully expect to establish an exact constraint, not just the lower bound side of one.

@samwgoldman I though it was the case, but how does it explain this:

type A = $ObjMapi<{ FOO: null }, <K>(k:K) => K>
declare var a: A;

(a.FOO : 'FOO'); // ok
a.FOO = 'BAR'; // no error, a.FOO should be 'FOO' | 'BAR'
(a.FOO : 'FOO'); // error

That's caused by definite assignment analysis. On that line the value is definitely BAR. Wrap in a conditional or otherwise havoc the refinements to get the union.

Hey, just wanted to see if there were any plans for this. Would be pretty cool to have this working properly!

These bugs were fixed a couple versions back. Sorry for not closing this sooner 馃槉

This is a great news, thanks @calebmer

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jamesisaac picture jamesisaac  路  44Comments

sophiebits picture sophiebits  路  66Comments

jlongster picture jlongster  路  55Comments

gabro picture gabro  路  57Comments

TylerEich picture TylerEich  路  49Comments