Reputation: 5825
I have a generic type Group
that looks like this:
// K -> Key
// I -> Input
type Group<K, I> = {
key: K;
func: (i: I) => void;
};
There is a fixed number of Group
values which I declared in an object like this:
const GROUPS = {
"a": {
func: (i: {x: number}) => { console.log(i); },
key: "a",
},
"b": {
func: (i: { y: number }) => { console.log(i) },
key: "b"
}
} as const;
I then have 2 utility types to refer to all the possible group keys and all the possible group inputs:
type GroupKey = keyof typeof GROUPS;
type GroupInput<K extends GroupKey> = Parameters<typeof GROUPS[K]["func"]>[0];
// GroupValue test:
type TestType = GroupInput<"b">; // { y: number}, this works
Finally, I have a function that receives both a group key and a group input:
function test<K extends GroupKey>(key: K, input: GroupInput<K>) {
if (key === "b") {
(input.y); // Why doesn't TypeScript understand that `input.y` must be `number` here?
}
}
This function is generic over the type of the key that is passed in and unfortunately, TypeScript cannot "understand" that if key
is "b"
, then input
is of type { y: number }
. Why is this the case, what is TypeScript missing to be able to do this? I'd especially like to find a GitHub issue on this (so that I can subscribe to it), but I wasn't able to find one as this type of thing is particularly hard to search for.
Upvotes: 3
Views: 185
Reputation: 20348
Please consider this snippet:
const key = 'a' as GroupKey
const input = { y: 1 } // optionally cast as GroupInput<'b'> or as GroupInput<GroupKey>
test(key, input) // compiles, but not intended
The input
might be independent from the key
. There are no guarantees input.y
must be a number when test
is called with value 'b'
as the first argument.
type TestType = GroupInput<"b">
uses literal type ('b'
) which allows Typescript to restrict 'a' | 'b'
to just 'b'
. The same applies for test('b', ...)
, but passing key of type 'a' | 'b'
allows to pass input of type GroupInput<'a' | 'b'>
.
One option would be to check if 'y' in input
, but this still does not address the main issue of not allowing wrong arguments to test
. In general case input as GroupInput<'b'>
is unsafe and should be avoided at any cost.
A possible fix:
type Params = { [K in GroupKey]: [key: K, input: GroupInput<K>] } // key: and input: are used for auto-completion instead of generic arg_0, arg_1
function test2(...args: Params[GroupKey]) {
// const [key, input] = args // will not work
if (args[0] === "b") {
const input = args[1];
input.y; // number
}
}
test2('b', { y: 1 }) // ok
test2('b', { x: 1 }) // error
test2(key, input) // error
Upvotes: 2
Reputation: 6998
Because the input
parameter is independent from your key
parameter. input
is not required to have y
as a property although your key might be equal to 'b'
. You would have to Typecast your input
:
function test<K extends GroupKey>(key: K, input: GroupInput<K>) {
if (key === "b") {
const typedInput = input as GroupInput<'b'>;
(typedInput.y)
}
}
Upvotes: 0