Search code examples
typescriptmapped-types

Issue around Mapped Tuple Types with type inference


I'm trying to wrap my head around a problem concerning mapped tuple types. They appear to work when I explicitly define a type to map, but when the type to map is inferred via a generic, the type-system appears to not work correctly.

class Boxed<T> {
  constructor(private arg: T){ }
}
type MapToRaw<T> = { [K in keyof T]: T[K] extends Boxed<infer E> ? E : never };

// WORKS AS EXPECTED
let val: MapToRaw<[Boxed<number>, Boxed<string>]>;
// 'val' is of type [number, string]

// DOESN'T WORK AS EXPECTED
let fn = <X extends Boxed<any>[]>(arg: X) => null as any as MapToRaw<X>;
let res = fn([new Boxed(0), new Boxed('')]);
// 'res' is of type (string | number)[]

I actually don't think there is a way to do this without doing the following:

let fn = <X extends Boxed<any>[]>(...arg: X) => null as any as MapToRaw<X>;
let res = fn(new Boxed(0), new Boxed(''));
// 'res ' is of type [number, string]

but in my case, I'd prefer to pass in an array rather than variadic arguments


Solution

  • You need to hint to the compiler that B should be a tuple, not just a simple array. You can do this with a type constraint of [T] | T[]:

    class Boxed<T> {
      constructor(private arg: T){ }
    }
    type MapToRaw<T> = { [K in keyof T]: T[K] extends Boxed<infer E> ? E : never };
    
    let fn = <X extends [Boxed<any>] | Boxed<any>[]>(arg: X) => null! as MapToRaw<X>;
    let res = fn([new Boxed(0), new Boxed('')]);
    

    Playground Link