Home > Mobile >  Properly typing a filter & flatten function in TypeScript
Properly typing a filter & flatten function in TypeScript

Time:12-15

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;
};

Playground link

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

  • Related