As shown in the playground, I have two functions "noneIs" and "someIs" which check if either none or some item in the given array is of a particular type.
It's important to note that !someIs(arr, type) == noneIs(arr, type) When using !someIs the type is not inferred correctly, can anyone suggest a way to fix this?
CodePudding user response:
Will something like this work for you?...
type A = {type: "a", i: number};
type B = {type: "b", someOtherProp: string};
type C = {type: "c", i: number};
type Union = A | B | C;
// returns true if no item in array "arr" is of type "type"
const noneIs =
<K extends Union["type"]>
(arr: Union[], type: K):
arr is Exclude<Union, Union & {type: K}>[] =>
!arr.some(v => v.type == type);
// returns true if some item in array "arr" is of type "type"
const someIs =
<K extends Union["type"]>
(arr: Union[], type: K) =>
!noneIs(arr, type);
// example array of Union types
const arr: Union[] = [
{type: "a", i: 1},
{type: "c", i: 2}
];
// this works as expected
if(noneIs(arr, "b")){
arr[0].i;
}
// Even this doesn't work
// if(!noneIs(arr, "b")){
// arr[0].i;
// arr[0].someOtherProp;
// }
// but this does not
if(!someIs(arr, "b")){
// arr[0].i // <---- Not working
(arr[0] as Exclude<Union, B>).i; // <--- Working, Explicit Type assertion to make it work
}
CodePudding user response:
I'll reiterate my comment on your question and support it with an example:
Even if you inform the compiler that at least one of the array elements matches the type, the compiler still doesn't know which element that is, so the someIs predicate doesn't help to narrow the array. You'll need to narrow individual elements to use them safely (this is related to the same principle which is addressed by the compiler option noUncheckedIndexedAccess).
type A = {type: 'a', i: number};
type B = {type: 'b', someOtherProp: string};
type C = {type: 'c', i: number};
type Union = A | B | C;
// Refactor of your example
function noneAre <T extends Union['type'], A extends readonly Union[]>(
type: T,
arr: A,
): arr is { [K in keyof A]: Exclude<A[K], { type: T }> } {
return arr.every(v => v.type !== type);
}
// Narrow a single `Union` element to a specific type
function isType <
T extends Union['type'],
V extends Union,
>(type: T, value: V): value is V & { type: T } {
return value.type === type;
}
///// Examples:
const a: A = {type: 'a', i: 1};
const b: B = {type: 'b', someOtherProp: 'str'};
const c: C = {type: 'c', i: 1};
const arr = [a, c]; // (A | C)[]
if(noneAre('a', arr)) arr; // arr is C[]
if(noneAre('c', arr)) arr; // arr is A[]
const tuple = [a, c] as [A, C]; // [A, C]
if(noneAre('a', tuple)){
tuple; // tuple is [never, C]
tuple[0].i; /*
^
Property 'i' does not exist on type 'never'.(2339) */
tuple[1].i; // number
}
const arr2: Union[] = [b, a, c, c, a /* ... */];
if(isType('b', arr2[0])){
const [
first, // element at index 0 is B
second, // element at index 1 is Union
third, // element at index 2 is Union
// ...etc.
] = arr2;
}
// Alternatively
const [first] = arr2; // first is Union
if (isType('c', first)) {
first; // C
}
else if (isType('a', first)) {
first; // A
}
else {
first; // B
}
