Search code examples
typescripttuplestype-inferencemetatype

TypeScript convert tuple type with nullable type to tuple with non-nullable type


I am going to function to memoize return values from the function given.

const memoize = (value, onHandle) => {
  for (let index = 0; index < value.length; index++) {
    if (value[index] === null || value[index] === undefined) {
      return
    }
  }
  //
  // Check if there is memoized value with same parameters.
  //
  return onHandle(value)
}

The callback function onHandle receives non nullable values and return a value with being memoized to cache object.
I should declare types that convert tuples with nullable type to tuples with non nullable like this below.

type Tuple = [number | undefined, string, boolean, string | undefined]
type StrictTuple = [number, string, boolean, string]

I have tried several times, but inferring tuple with non nullable is so challenging actually. Are there any ways to draw tuple type with non nullable value only?

Here are the failed code.

type StrictTuple<T extends [unknown, ...unknown[]]> = T extends [
  unknown,
  ...unknown[]
]
  ? T[number] extends null | undefined
    ? never
    : T[number] extends infer U
    ? U extends null | undefined
      ? never
      : T
    : never
  : never;
type Example1 = StrictTuple<[1 | undefined, 2, "3", true | null, boolean]> 
// Example1 shows [1 | undefined, 2, "3", true | null, boolean]
type StrictTuple<T extends [E, ...E[]], E = unknown> = T[number] extends infer U ? U extends null | undefined ? never : [U, ...U[]]: never
type Example2 = StrictTuple<[1, 2, "3", boolean, number | undefined]>
// Example2 shows [number, ...number[]] | [false, ...false[]] | [true, ...true[]] | ["3", ..."3"[]]

Solution

  • You can write a mapped type to do this:

    type Strictify<T> = { [K in keyof T]-?: T[K] & {} }
    

    TypeScript automatically produces tuple/array types when mapping over generic tuple/array types. The definition uses the -? mapping modifier to say that any optional properties should be made required. Each property value type is intersected with the empty object type {} to remove null and undefined from the domain. I also could have used the NonNullable<> utility type which is (now) implemented as such an intersection.

    Let's test it:

    type Example1 = Strictify<[number | undefined, string, boolean, string | undefined];
    //   ^? type Example1 = [number, string, boolean, string]
    
    type Example2 = Strictify<[1, 2, "3", boolean, number | undefined]>;
    //   ^? type Example2 = [1, 2, "3", boolean, number]
    
    type Example3 = Strictify<[string | null, number | undefined, boolean?]>;
    //   ^? type Example3 = [string, number, boolean]
    

    Looks good. Note that I didn't call this StrictTuple because it also works on non-arraylike objects:

    type ExampleObj = Strictify<{ a: string, b?: number, c: boolean | null, d: Date };
    /*   ^? type ExampleObj = {
        a: string;
        b: number;
        c: boolean;
        d: Date;
    } */
    

    Playground link to code