I need to narrow down the type of a variable that is a union of different array types in order to run specific code for each type. I attempted to use Array.every
along with a custom type guard, but encountered an error in TypeScript stating "This expression is not callable," with a confusing explanation.
Here is a simplified example:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if (unionArr.every(isNumber)) { // <- Error
unionArr;
}
The error message reads:
This expression is not callable.
Each member of the union type
'{
<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
} | {
...;
}'
has signatures, but none of those signatures are compatible with each other.
To resolve this issue, I found that using a type assertion to convert my array to unknown[]
before narrowing its type, or changing the array from string[] | number[]
to (string | number)[]
helped eliminate the error without compromising type safety.
However, even after these adjustments, further type assertions were needed to correctly narrow the array type:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const unionArr: string[] | number[] = Math.random() > 0.5 ? [1, 2, 3, 4, 5] : ['1', '2', '3', '4', '5'];
if ((unionArr as unknown[]).every(isNumber)) { // <- No error
unionArr; // <- Incorrectly typed as string[] | number[]
}
if ((unionArr as (string | number)[]).every(isNumber)) { // <- No error
unionArr; // <- Type incorrectly remains as string[] | number[]
}
A similar comparison with a non-array union did not yield any errors and successfully narrowed the type:
const isNumber = (val: unknown): val is number => typeof val === 'number';
const union: string | number = Math.random() > 0.5 ? 1 : '1';
if (isNumber(union)) {
union; // <- Correctly typed as number
}
While the workaround works fine, the root cause of the error still remains unclear. It seems to be related to how TypeScript interprets the typings of Array.every
, and despite trying various approaches, the type assertion as unknown[]
seems to be the most suitable solution. If there are better alternatives or if something could be done differently, please feel free to share your insights!