Reputation: 2685
I'm trying to force an argument of type number[]
to contain at least one element of value 9
.
So far I've got:
type MyType<Required> = { 0: Required } | { 1: Required } | { 2: Required };
declare function forceInArray<
Required extends number,
Type extends number[] & MyType<Required>
>(
required: Required,
input: Type
): void;
// should fail type-checking
forceInArray(9, []);
forceInArray(9, [1, 2]);
forceInArray(9, { 0: 9 });
// should type-check correctly
forceInArray(9, [9]);
forceInArray(9, [9, 9]);
forceInArray(9, [9, 2, 3, 4]);
forceInArray(9, [1, 9, 3, 4]);
forceInArray(9, [1, 2, 9, 4]);
forceInArray(9, [1, 2, 3, 9]);
But ofc the type MyType
won't include all possible indexes, so I'm trying to write that in some other way. { [index: number]: 9}
is not the good way to do that, since it requires all values to be set to 9
. I've also tried some combination of mapped types, with no success
How can I write MyType
so that it solves this problem?
Upvotes: 4
Views: 2252
Reputation: 328097
You can indeed use mapped types. Here's how I'd type forceInArray()
:
declare function forceInArray<
R extends number,
T extends number[],
>(required: R, input: [...T] extends { [K in keyof T]: { [P in K]: R } }[number] ?
readonly [...T] : never): void;
Some of the complexity here has to do with convincing the compiler to infer array literal values as tuple types and number literal values as numeric literal types (having [...T]
in there deals with both). There's some black magic involved. Also I'd expect some interesting edge cases to crop up around widened types like number
, 0-element tuples, etc. Finally, I used readonly
arrays so people can use const
assertions if they want (as in forceInArray(9, [1,2,9] as const)
).
Okay, the heart of the matter: { [ K in keyof T]: { [P in K]: R } }[number]
type is very much like your MyType
type alias. If T
is [4, 5, 6, 7, 8]
and R
is 9
, then that type becomes [{0: 9}, {1: 9}, {2: 9}, {3: 9}, {4: 9}][number]
, or {0: 9} | {1: 9} | {2: 9} | {3: 9} | {4: 9}
. Notice how it expands to have as many terms as the length of T
.
Let's see if it works:
forceInArray(9, []); // error
forceInArray(9, [1, 2]); // error
forceInArray(9, { 0: 9 }); // error
forceInArray(9, [9]); // okay
forceInArray(9, [9, 9]); // okay
forceInArray(9, [9, 2, 3, 4]); // okay
forceInArray(9, [1, 9, 3, 4]); // okay
forceInArray(9, [1, 2, 9, 4]); // okay
forceInArray(9, [1, 2, 3, 9]); // okay
forceInArray(9, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // okay
Looks good. Hope that helps; good luck!
Upvotes: 3