Search code examples
typescripttypestuplesvariadic-functions

TypeScript: inferring data types from variadic tuple types


Variadic tuple types allow us to provide the caller of a function, a tuple return type driven by the tuple supplied to the function like so:

function makeArrayAsConstItems<T extends readonly any[]>(...arr: [...T]): {[K in keyof T]: {item: T[K]} } {
    return arr.map(item => ({ item })) as any;
}

const arrayAsConstItems = makeArrayAsConstItems('cfgh', 1, new Date());

// this is the type
const arrayAsConstItems: [{
    item: string;
}, {
    item: number;
}, {
    item: Date;
}]

Now consider we want to do two more things:

  1. Narrow the items that can be supplied in each array element to be a type of Data<TypeOfData>
  2. Extract the type we're going to be returning from Data<TypeOfData>; so use TypeOfData essentially
interface Data<TypeOfData = unknown> {
    data: TypeOfData;
}

function makeArrayAsConstItemsForDataTypesOnly<T extends readonly Data[]>(...arr: [...T]): {[K in keyof T]: {item: T[K]} } {
    return arr.map(item => ({ item })) as any;
}

const arrayAsConstItemsForDataTypesOnly = makeArrayAsConstItemsForDataTypesOnly(
    { data: 'cfgh' }, 
    { data: 1 }, 
    { data: new Date() }
)

What would we need to do for arrayAsConstItemsForDataTypesOnly to have the same type as arrayAsConstItems?

At present the type is:

const arrayAsConstItemsForDataTypesOnly: [{
    item: {
        data: string;
    };
}, {
    item: {
        data: number;
    };
}, {
    item: {
        data: Date;
    };
}]

And we desire it to be:

const arrayAsConstItemsForDataTypesOnly: [{
    item: string;
}, {
    item: number;
}, {
    item: Date;
}]

This is @A_blop's suggestion:

function makeArrayAsConstItemsForDataTypesOnly<T extends readonly Data[]>(...arr: [...T]): {[K in keyof T]: {item: Extract<T[K], Data>["data"]} } {
    return arr.map(item => ({ item })) as any;
}

Solution

  • We can do that by utilizing lookup types in combination with the Extract utility type:

    function fn<T extends readonly Data[]>(...arr: [...T]):
        { [K in keyof T]: { item: Extract<T[K], Data>["data"] } } {/* impl */}
    
    const arrayAsConstItemsForDataTypesOnly = fn(
        { data: 'cfgh' },
        { data: 1 },
        { data: new Date() }
    )
    /*
    [{
        item: string;
    }, {
        item: number;
    }, {
        item: Date;
    }]
     */
    

    As TypeScript cannot resolve T[K] further, we need to first convince it again, that the resulting type has a data property accessible via lookup operator. This is done by Extract.

    Playground