Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsealed objects are not type-checked #2327

Closed
vkurchatkin opened this issue Aug 24, 2016 · 4 comments
Closed

Unsealed objects are not type-checked #2327

vkurchatkin opened this issue Aug 24, 2016 · 4 comments

Comments

@vkurchatkin
Copy link
Contributor

This passes:

const c = {};

c.foo();

This doesn't:

const c = { x: 1 };

c.foo();
@vkurchatkin
Copy link
Contributor Author

Also, inconsistency: this works fine:

declare function test(t: { x: number }): void;

const c = {};

test(c); // error

c.x = 1;
// no error if we call test here

But this works incorrectly:

declare function test(t: { x: number }): void;

const c = {};

test({ x: c.x }); // no error

@samwgoldman
Copy link
Member

This is definitely surprising behavior, but this is actually working as designed. Here's a relevant snippet from the docs

However, for a property that may be added to an object after its creation, Flow cannot guarantee the existence of that property at a particular property access operation; it can only check that its writes and reads are type- consistent. Providing such guarantees for dynamic objects would significantly complicate the analysis; this is a well-known fact (in technical terms, Flow’s analysis is heap-insensitive for strong updates).

This means that Flow will not catch read-before-write order problems for unsealed objects like this:

var o = {};
(o.p: number); // read
o.p = 0; // write is consistent, no error

But it will catch inconsistent read/writes:

var o = {};
(o.p: string); // read
o.p = 0; // error: number -> string

If you only read and write properties, this behavior is fairly predictable. You've astutely noted one inconsistency with this story, though. Checking an unsealed object against another object is "strict" whereas unsealed object property read/writes are "non-strict."

var o: { p: string } = {}; // error: property `p` not found

You might (reasonably) interpret the above as similar to a property read. It would be reasonable under that interpretation to not error here. Currently we special-case this particular pattern because a) this pattern is less common and b) it's especially surprising.

This non-strict read/write behavior is important to support a common JS idiom of building objects incrementally. This doesn't just happen with objects, but also ES3-style classes, where methods are incrementally added to the function's prototype object.

For some added color, I have a few diffs in the pipeline that work in this part of the code, although those diffs largely maintain existing behavior including everything I've described here.

Hope this helps clear things up!

@vkurchatkin
Copy link
Contributor Author

This non-strict read/write behavior is important to support a common JS idiom of building objects incrementally

Can you provide an example? I actually do exactly that and I can't see how strictness can get in the way.

Maybe such behaviour can be implemented with an explicit type (e.g. var o: $UnsealedObject ={})?

@gkz
Copy link
Member

gkz commented Jul 13, 2022

Fixed if you enabled exact_empty_objects=true in your .flowconfig.
Announcement will be in the future

@gkz gkz closed this as completed Jul 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants