I have a variable with a type that is a union of different array types. I want to narrow its type to a single member of that union, so I can run the appropriate code over it for each type. Using Array.every and a custom type guard seems like the right approach here, but when I try this TypeScript complains that "This expression is not callable." along with an explanation that I don't understand.
Here is my minimum reproducible 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;
}
Here is the error:
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.
This isn't preventing me from continuing. I've found that using a type assertion to recast my array to unknown[] before I narrow its type, as I would do if I were writing an isNumberArray custom type guard, removes the error without compromising type safety.
I've also found that recasting my string[] | number[] array to (string | number)[] removes the error.
However, the type of the array doesn't seem to be narrowed correctly, so I would need to use an additional as number[] after the check:
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; // <- Incrrectly typed as string[] | number[]
}
I tried a comparison with a non-array union as well, though of course in this case I was just using the custom type guard directly instead of using it with Array.every. In that case, there was also no error and the type was narrowed correctly:
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
}
Because I have that safe type assertion workaround, I can continue without needing to understand this. But I'm still very confused as to why that error appears in the first place, given I am trying to narrow a union of types to a single member of that union.
I'm guessing this is something to do with how Array.every has been typed by TypeScript, and there's probably nothing I can do aside from the workaround I'm already using. But it's hard to be sure of that when I don't really understand what's going wrong. Is there something I could do differently here, or is the as unknown[] type assertion I've used the correct or best way to handle this?