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
You can indeed use mapped types. Here's how I'd type forceInArray()
:
declare function forceInArray<
R extends number,
T extends (ReadonlyArray<number> | readonly [R]) &
{ [K in keyof T]: { [P in K]: R } }[number]
>(required: R, input: T): 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 [R]
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!
Link to code