Given the following function definition:
const filterNonUndefined = <T>(vals: (T | T[] | undefined)[]): T[] => {
const nonUndefined = vals.filter((v): v is (T | T[]) => v !== undefined);
return nonUndefined.flat();
};
The compiler complains that:
Type '(T | (T extends readonly (infer InnerArr)[] ? InnerArr : T))[]' is not assignable to type 'T[]'.
Type 'T | (T extends readonly (infer InnerArr)[] ? InnerArr : T)' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'T | (T extends readonly (infer InnerArr)[] ? InnerArr : T)'.
To my understanding, nonUndefined
has type (T | T[])[]
. I would expect that flattening that would result in T[]
.
I am not sure I understand why the error occurs and how I can properly fix it.
Please note that I have already reviewed this question and it does not answer my problem.
CodePudding user response:
If you add a temp variable for the final result, you will see that .flat
returns a slightly more complex type than T[]
. Although to me it does look like they are equivalent, TS does not think so. The working version is:
const filterNonUndefined = <T>(vals: (T | T[] | undefined)[]): (T | (T extends readonly (infer InnerArr)[] ? InnerArr : T))[] => {
const nonUndefined = vals.filter((v): v is (T | T[]) => v !== undefined);
const result = nonUndefined.flat();
return result;
};
The inference of the deep array elements appears to be required so that the .flat
function supports arrays with elements of mixed types.
CodePudding user response:
Technically, the error is correct, as T may be an array
I would recommend to just remove the return type clause and lay on inferret return type which is correct
function nonNullable<T>(value: T | null | undefined): value is T {
return value != null
}
const filterNonUndefined = <T>(vals: (T | T[] | undefined)[]) /* : auto */ => {
// (T | (T extends readonly (infer InnerArr)[] ? InnerArr : T))[]
return vals.filter(nonNullable).flat();
};
let a = filterNonUndefined([1, 2, 3, [1, 2, 3]])
// ^?
// let a: number[]
let c = [1, 2, 3] as const;
let b = filterNonUndefined([c, c, c, [c, c, c]])
// ^?
// let b: (1 | 2 | 3 | readonly [1, 2, 3])[]
If you must have return type (because ts-eslint/return-types
or whatever), copy the inferred correct type from popup