-
-
Notifications
You must be signed in to change notification settings - Fork 654
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
Flow-related work on the path to RN v0.64 upgrade, part 2. #4520
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @chrisbobbe ! Generally this looks great. A handful of comments below.
In the interest of simplifying iteration, I'm going to go ahead and merge the 35/39 commits that don't appear in these comments 🙂
src/boot/store.js
Outdated
const result: $Shape<GlobalState> = {}; | ||
storeKeys.forEach(key => { | ||
// $FlowFixMe[incompatible-indexer] | ||
// $FlowFixMe[incompatible-exact] | ||
// $FlowFixMe[prop-missing] | ||
// $FlowFixMe[incompatible-variance] | ||
// $FlowFixMe[incompatible-type-arg] | ||
/* $FlowFixMe[incompatible-type] | ||
/* $FlowFixMe[cannot-write] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lets us remove several suppressions at one site -- probably not
because they don't apply anymore, but because Flow doesn't bother to
check for them once it sees the most obvious problem.
😁
It makes sense for GlobalState
itself to be read-only, but for result
here we really want the read-write version of it -- we're doing these mutations (which would be perfectly well-typed, if only we could lead Flow to use the fact that it's the same key
twice), and then we'll return the result and it's fine that the caller promises not to mutate the object further.
One hack for expressing that would be like this:
- const result: $Shape<GlobalState> = {};
+ const result: $Shape<$Diff<GlobalState, {||}>> = {};
Doesn't super matter here, though it would make the explanation in the comment make a bit more sense. Could be more important somewhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe the right answer is that there should be a ReadWrite<T>
defined in our generics.js
that encodes that trick. Then we'd just use that here.
The $Rest<Foo, { ... }>
trick that comes up a lot in exampleData
, to make all the properties optional, is another one that'd be good to give that treatment to. (Came to mind because there were a bunch of instances of it in the first commit of this PR.) Maybe named Shape
? GoodShape
? It's basically a fixed version of $Shape
, which is unsound (https://flow.org/en/docs/types/utilities/#toc-shape .)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe the right answer is that there should be a
ReadWrite<T>
defined in ourgenerics.js
that encodes that trick. Then we'd just use that here.
That sounds good.
My first thought was that maybe we could implement ReadWrite<T>
using $ObjMap
instead of $Diff
:
-
$ObjMap
is singled out as having the behavior we want, in the doc, while$Diff
is not:Additionally, other utility types, such as
$ObjMap<T>
, may strip any read/write annotations [...](Hm, though I guess it says "may", so it's not for certain.)
-
The bug in
$Diff
, which we'd be relying on forReadWrite
, is documented at $Diff<A, B> causes property variance in A to be lost in resulting type facebook/flow#6225, and is potentially on its way to being fixed. If it got fixed, ourReadWrite
would break. -
Someone commented on that Flow issue, linking to a PR, $ObjMap property modifiers facebook/flow#7643, which suggests that
$ObjMap
might be on its way to growing a nice feature that would help us implementReadWrite
quite transparently, with$ObjMap
, with just a small code change.
Sadly I haven't gotten $ObjMap
to work as it is currently:
type ReadWrite<T> = $ObjMap<T, <V>(V) => V>;
When I try to use this, Flow still complains with the cannot-write
code; you can see this happening on the latest Flow version (alongside an implementation with $Diff
that does work) in this repro (let me know if the link doesn't work).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think I'll go with $Diff
for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
$Rest<Foo, { ... }>
trick that comes up a lot inexampleData
, to make all the properties optional, is another one that'd be good to give that treatment to. (Came to mind because there were a bunch of instances of it in the first commit of this PR.) Maybe namedShape
?GoodShape
? It's basically a fixed version of$Shape
, which is unsound (https://flow.org/en/docs/types/utilities/#toc-shape .)
Hmm, yeah, good idea. I see from f7354c1 that $ReadOnly<$Shape<…>>
is reportedly sound, while just $Shape
is not. Do you think that's right, and do you think that may sensibly remove the need for a Shape
, GoodShape
, etc.? I think it's a general goal to mark more things $ReadOnly
than we do currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see from f7354c1 that
$ReadOnly<$Shape<…>>
is reportedly sound, while just$Shape
is not.
Hmm, it does say that, but it's not clear what source or reasoning that's based on. In particular that's not mentioned in the upstream docs that point out $Shape
is unsound.
Just from a quick look at the example from there in the docs and playing with that in the Flow playground, I think in fact it's not true. Here's the example, with $ReadOnly
added:
type Person = $ReadOnly<{
age: number,
name: string,
}>;
type PersonDetails = $ReadOnly<$Shape<Person>>;
const personShape: PersonDetails = {age: 28};
(personShape: Person);
That actually does give an error… but only at the {age: 28}
literal. Which seems like its own bug -- that line ought to be fine.
And in fact if you adapt the example so it doesn't trip that bug, Flow unsoundly accepts it:
(personShape: PersonDetails): Person => personShape;
So while we do want most of our object types to be $ReadOnly
, and that might alleviate some of the symptoms of the unsoundness of $Shape
, I don't think it really solves it.
src/actionTypes.js
Outdated
@@ -102,7 +102,7 @@ import type { ZulipVersion } from './utils/zulipVersion'; | |||
*/ | |||
type RehydrateAction = {| | |||
type: typeof REHYDRATE, | |||
payload: GlobalState | { accounts: null } | {||} | void, | |||
payload: $ObjMapi<$ReadOnly<$Shape<GlobalState>>, <K, V>() => V | null> | void, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can simplify this a bit with plain $ObjMap
, and dropping the K
parameter.
Hmm, also I think the parameters need to be parameter types of that function type. So like <K, V>(K, V) => V | null
.
One reason that's the API is that it actually lets you put more structure in the parameter types -- effectively destructuring them -- like in this example in the docs:
type ExtractReturnObjectType = <K, V>(K, () => V) => { k: K, v: V };
declare function run<O: Object>(o: O): $ObjMapi<O, ExtractReturnObjectType>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, also I think the parameters need to be parameter types of that function type. So like
<K, V>(K, V) => V | null
.
Ah, yup. As is, in the spot in redux-persist-migrate/index.js
where we make one of these, we can put nonsense like { ...migratedState, accounts: 123 }
there and with this type Flow doesn't complain. 🙂
Then once this is fixed, Flow does find an error in one of the handful of places we actually consume this type:
const { payload } = action;
const haveApiKey = !!(payload && payload.accounts && hasAuth(payload));
saying hasAuth
can't accept that payload because all the properties could be null.
A fixme would be pretty reasonable there. But then that actually would make this type lead to less type-checking coverage -- once there's a fixme on that hasAuth(payload)
, Flow wouldn't complain if that payload.accounts &&
check went away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about unsoundly casting payload
(just where it's passed to hasAuth
) to GlobalState
, and ignoring specifically that cast and nothing else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that still doesn't quite get what I want; but I'll push a commit at the end of the branch that revises it further.
In the end I think it winds up better than the code was before -- in particular it really wasn't great for this line over in src/actionTypes.js
to have the job of encoding some assumptions that sessionReducer.js
is making. We do want something like this about specifically accounts
, but we can make it local right next to the code that it's really about.
Merged these 35 commits: 3fff26a exampleData: Convert object types to exact, or explicitly inexact. That leaves these 4: 6060c42 NotSubscribed types: Annotate |
Good plan; thanks! I'll address those comments and get another revision in soon. 🙂 |
An instance of zulip#3452. See discussion at zulip#4520 (comment).
And, for the one `GlobalState`-shaped variable that we want to write to, use the recently added `ReadWrite` generic. See discussion at zulip#4520 (comment).
dc86ebd
to
63b3e65
Compare
Thanks for the review! Revision pushed, and please see my comments above. |
An instance of zulip#3452. See discussion at zulip#4520 (comment).
Just rebased past cd29cb9 (I'd forgotten that this old PR also included making |
This is more accurate because we do intend to write to `result`.
An instance of zulip#3452. See discussion at zulip#4520 (comment).
The `{ accounts: null }` part, added in 0ae4c0c, looks like a hack that's meant to represent the "`null` could be at any key" part. So, write a more rigorous type to account for that.
Also update a comment pointing to the related code that determines this logic.
Thanks for the revision! Looks good -- merged, with an additional commit on top. See also replies in threads above: #4520 (comment) and #4520 (comment) . |
Thanks for the review!! |
An instance of zulip#4837. Making sure to keep our chosen annotation for `stream`, which is looser than `getStreamInNarrow`'s return type. See b21bf43 and zulip#4520 (comment).
An instance of zulip#4837. Making sure to keep our chosen annotation for `stream`, which is looser than `getStreamInNarrow`'s return type. See b21bf43 and zulip#4520 (comment).
An instance of zulip#4837. Making sure to keep our chosen annotation for `stream`, which is looser than `getStreamInNarrow`'s return type. See b21bf43 and zulip#4520 (comment).
This addresses the portion of #3452 (which we should do before the RN v0.64 upgrade, #4426) in our own code, while leaving 36 sites in the libdefs, to be treated in another PR.
Done by temporarily adding
implicit-inexact-object=warn
to our .flowconfig and fixing all the warnings it flags, as Greg recommends in #3452 (comment).My focus has been to satisfy the lint rule kind of quickly, using exact (over explicitly inexact) when it's convenient and obviously correct. It's quite possible that more of these could have been made exact—but I don't expect this to cause any type-checking regressions, as explicitly inexact is equivalent to implicitly inexact to the type-checker.
Please give special attention to the last two commits—the rehydrate payload type was looking pretty incorrect, and I think I've improved it. I found that that improvement was possible after making
GlobalState
readonly, which I do in the second-to-last commit; see discussion where I ask if this is OK.