Flow version: 0.93 (and previous)
In the following code:
~~~js
const enumObj = {
'one': null,
'two': null,
}
export type Enum = $Keys
const getEnum = (input : string): ?Enum => {
const k = Object.keys(enumObj)
return k.find(tbf => tbf === input)
}
// Errors as expected
const getEnum2 = (input : string): ?Enum => {
return input in enumObj ? input : undefined
}
~~~
the function getEnum doesn't error, but it should. Also,
Why is k: Array<any | string>, if we use find on k, but if we don't use find, it "stays" as k: string[]?
In the libdefs, the this arg of find has type any, so when you use k.find here that any propagates to the type of k. To fix this, annotate k as Array<string>.
Hello @dsainati1 Thanks for responding! Could you explain/clarify how and why this propagates? I'm trying to better understand what's going on here.
~js
declare class $ReadOnlyArray<+T> {
// …
find(callbackfn: (value: T, index: number, array: $ReadOnlyArray
// …
}
~
Essentially, k becomes the thisArg of the function call, generating a type constraint requiring that the the type of k be a subtype of any. Let's call this subtype U. We already know that it must be an Array because we are calling an Array function on it, and that it must contain at least strings, because we assigned the result of Object.keys to it. Thus we can determine that U <: Array<V> (where V <: string) and T <: any. Because arrays are invariant with respect to subtyping, we thus have the constraint that Array<V> <: any, which becomes V <: any and any <: V (along with V <: string). The best type to solve this contraint is V = string | any, so we pick that.
@dsainati1 Thank you for the details! I'm still a bit confused about the part in the first sentence, but the rest makes sense. How does flow decide that k becomes thisArg here?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
Essentially because find is being called on k.
I understand how this works in JavaScript, but I am still confused how Flow came to this conclusion. It seems like quite a leap, and an incorrect one. Can you give me an example where this conclusion makes sense? That might clear it up, hopefully.
Sorry to bother you so much with this.
Ah I think I misunderstood how this libdef works; Flow is not generating a constraint between thisArg and k, but rather between the this of the Array class and k. We model the this as an any because the class and its instance are in different files.
Flow is not generating a constraint between
thisArgandk, but rather between thethisof theArrayclass andk. We model thethisas ananybecause the class and its instance are in different files.
So, looking at the Array<T> typedef, I don't see this being set to any anywhere in there?
Where is Array's this modeled as any? Also, I have no idea what that last sentence (about the class and the instance being in different files) means. :slightly_frowning_face:
That part doesn't happen in the libdef; it's internal to Flow. Right now as a limitation of the typechecker we model this types as any. I guess in that sense this behavior is consistent with what we expect from the typechecker, but undesirable. We are working on improving our model of any at the moment.
Thank you for responding to all my questions!
Just to clarify I understood correctly:
We are working on improving our model of
anyat the moment.
Did you mean this instead of any here?
Also, is there an issue where we can track what's going on with this (no pun intended :yum:)?
Both actually. And I don't think there is at the moment, but I suspect we might have more to say on this subject soon.