Flow and Object.assign
13 March 2016
Flow has some helpful behavior for object literals:
/* @flow */
type T = {x: number};
const o = {};
o.x = 1;
(o : T);
Without the o.x = 1;
line, the cast to T
will fail:
src/flow-test.js:5
5: (o : T);
^ property `x`. Property not found in
5: (o : T);
^ object literal
Because the object literal is empty, it is “unsealed”. If it was created as const o = {y: 2};
, Flow would fail on o.x = 1;
src/flow-test.js:4
4: o.x = 1;
^ property `x`. Property not found in
4: o.x = 1;
^ object literal
This same Flow behavior comes into play with Object.assign()
.
/* @flow */
Object.assign({x: 1}, {y: 2});
{x: 1}
is sealed, so the property y
can’t be added.
src/flow-test.js:2
2: Object.assign({x: 1}, {y: 2});
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `y` of object literal. Property not found in
2: Object.assign({x: 1}, {y: 2});
^^^^^^ object literal
Object.assign({}, {x: 1}, {y: 2});
will work, because {}
is unsealed. Flow knows the shape of the result. Running flow suggest
:
src/flow-test.js
--- old
+++ new
@@ -1,2 +1,2 @@
/* @flow */
-const o = Object.assign({}, {x: 1}, {y: 2});
+const o: {x: number, y: number} = Object.assign({}, {x: 1}, {y: 2});
o
is unsealed, which I don’t think you can tell using type-at-pos
. This has some unfortunate consequences in a less contrived example:
/* @flow */
type T1 = {x : number};
type T2 = {y : number};
type T3 = T1 & T2;
function merge(t1 : T1, t2 : T2) : T3 {
const result = Object.assign({}, t1, t2);
console.log(result.info.toString()); // at one time, T1 contained info, but it was removed
return result;
}
This typechecks, but because result
is unsealed, Flow won’t prevent the access of the possibly undefined property result.info
. Flow still suggests the {x: number, y: number}
type, even though this would cause the code not to typecheck.
I’m impressed by the inferences Flow is able to make, and I’d like it to be stricter about what it allows. There are very few locations where I’d make use of unsealed objects, so I’d like them to be optional–either globally, set in .flowconfig
, or as a type (UnsealedObject
instead of Object
?).