-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Better support for object types with union types indexer keys #5276
Comments
Maps don't require all possible keys to be set, that's the most important property. What you probably want is a way to create an object type from a give union of string literals. |
Why is that an important property? after all you can mix object maps and literals alike, that would cause a lot of confusion
I do, but I also need the guarantee that the resulting type's keys are all of the keys of the enum, otherwise I cant safely access those properties using an array of the union of string literals. I.e. const obj: {| [Enum]: number |} = {...}
const keys: Array<Enum> = Object.keys(obj) |
Also, flow allows for the |
Flow is also inconsistent with this: flow doesn't require that all keys are used when creating the object, but it does assume all keys are set when using the object. E.g.: type Enum = 'A' | 'B';
type EnumMap = { [Enum] : string };
var map : EnumMap = {};
map['A'].substring(0);
// No errors! (This is even worse with I think it would be a very good idea to let flow make a distinction between complete and incomplete maps. |
Nothing inconsistent about this, this works the same as arrays. |
I disagree, its very inconsistent;
Exactly my thoughts! |
This is only true for normal objects, not for map objects. Map objects are exactly like arrays in this respect.
Because that's exactly why maps exist. If you need a type, where specific properties are required, use normal object type.
They give some guarantees, but they are unsound in general. The same as arrays.
"Complete map" is just a normal object type. |
Thats bizarre, one would never think that an object map should behave like an array, in fact it should be far from it. The idea that one could define all the possible keys of a map in an union type is not very far fetched, in fact most typed languages do so, This idea is also very limiting, forcing type definitions to be lenient when it comes to union types in object maps will almost lead to bad type definitions. Take this example Flow is usually very strict when it comes to null/undefined values, but its not when it comes to object maps because of contradictions this set of rules, you're not guaranteeing the properties of the enum to be present in the object, therefore this object should never guarantee all of its values to be defined either |
Not trying to compare the two libs, but typescript sets an example of how to define this type correctly. type Enum = 'a' | 'b'
type Value {
[key in Enum]: number
}
const value: Value = { a: 3 } // error: missing `b` key |
@eloytoro your TypeScript demonstrates my point: it's not a map, it's an object type. In fact, in TypeScript you can't use unions as map types at all. |
If you could point me in the direction of achieving what the ts example shows I'll consider this issue solved |
@vkurchatkin I think I understand your point, you're saying that object maps in flow should be defined like However flow has some limitations even by that definition, such as For some reason this will not cause an error, even when there's absolutely no guarantee that And for some reason this does cause an error, even when its pretty clear that key can be no other than the union type |
Another flaw in that definition would be that flow's syntax allows object maps to be defined as objects, which creates a huge contradiction: Object types have to have all of its keys defined and allow for unknown keys to exist whereas map types dont have to have all of its keys defined and dont allow unknown keys to exist. This contradiction is the result of the design flaw I refer to |
I have also ran into this. I haven't read this entire post - just the OP. I ended up having to create an exact object using type ExtractPluginStateType = <P>(plugin: P) => Datastream$PluginSessionState;
export type Datastream$PluginStates = $ObjMap<
PluginTypes,
ExtractPluginStateType,
>; I think the exact object type with indexer is a good syntax for something like this. It would need to be able to discern the difference of It is pretty much a shortcut for doing something like this to end up with the same types: type Value = {|
xs: number,
sm: number,
|};
type Key = $Keys<Value>; |
Flow's definition of |
Yep, just saying that it can provide the utility needed to compose your types in many cases -- providing a similar effect in the meantime. Not arguing against the need for an exact map. |
Here's a workaround for the "exact enum key" case:
It gives the expected errors:
|
@shinster thanks! |
Does anyone have a good solution since inferred types are being deprecated? |
@jlkalberer I've made the following: type Character = 'a' | 'b'; const response: { [char in Character]: number } = { a: 1, b: 2 }; console.log(response.a); // 1 |
That doesn't compile |
@Tyrcheg - yeah, that looks like typescript to me. |
To check that obj contains all values from eum I use
|
@joelochlann and I had a situation where we wanted to create an object that would exhaustively handle every instance of a Union Type.
we wanted to be able to create an object for which the keys would be an exhaustive list of each instance of the Union Type, i.e
and we would expect this to fail:
, as This was our solution:
|
I think I nailed it:
It will complain if
Note: it works if you are mapping some object type keys into strings, so it is not exactly what TS was asking about, but will hopefully help or at least give a hint. |
@Danchoys - this is great. The |
Related #7862 |
Related PR #7849 |
Closing since we now have mapped type |
Flexible types
Right now flow will complain if you try to define an object that contains less properties than its defined type allows it to have, however this is not the case for types that have a indexer key defined for an enum.
In most typed languages enums should always be exhausted, however flow seems to ignore this by allowing less keys to be present in the object, which goes against the specified behavior for non-strict object types.
Solution
Special cases
The case where the enum both statically and generically defined
Here all we have to do is to take the most general case (string) and ignore the static definitions
Exact types
Right now flow just doesnt really know what to do here, I don't think there's a single object that matches this type, while it looks pretty well defined, flow disagrees
Solution
In the example above the
Value
type could behave as a "key comprehension" of theKey
union type in a way that it makes sure that the object has only keys defined by the union type, no more, no lessSpecial cases
Basically any general types should throw an error eagerly for trying to define all of the keys of an object generically. I.e.
Advantages
Now that we know which keys are in the object, calling
Object.keys
should return the$Keys
of its argumentTL;DR The example is pretty self explanatory:
https://flow.org/try/#0PTAEAEDMBsHsHcBQBLAtgB1gJwC6gEoCmAhgMZ6RayqgDkWJ5tA3IojgJ7qGgDShHUAF46ADwDOtUAB8641LTalYAO3F5YAIwBWABgBcoAN6yA2vw4BdQyoCuqTYSwyAvsOOgJhgMyg3IUHEAC1hbaAATUCcqZ0hsUFRkcXFkFQBzUABrAVAAA3lcxGU1DR0ARkMTUHMBa1A7BydXdyNPcR8AGkDUQwAWP1AA4NCI+tg8aOwi1XVQLW0AJkrqizqGx2c3EVavUF9-MGGwyMnY+MTk1IzswXzUQuLZ+e9lmqsbew2B7dA42B8BkMQscxhMsDEgA
The text was updated successfully, but these errors were encountered: