qn0361
qn0361

Reputation: 47

Is it possible to infer type of variable's property by it's another property value?

I'm trying to make TypeScript automagically infer the type of variable by it's property value;

Let's assume we have the following function

function ensureReturnTypeIsRight(...args: any[]): ReturnType {
    return Math.random() > 0.5 ? {
        title: 'prop2',
        params: ['1', '2', '3'],
    } : {
        title: 'prop1',
        params: undefined,
    };
}

And

type SomeMap = {
    'prop1': string,
    'prop2': (...args: string[]) => string,
}

type ReturnType = `
    Type that supposed to throw an error if title is not a key of SomeMap,
    to throw an error in case title is 'prop2' but no params were provided
    to throw an error in case title is 'prop1' but params were provided
    but not to throw an error in case everything is correct.
    When we try to assign a value to a variable of that type (in our case 'variable' is return value)
`

It can be done with generic type like this

type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;
type GenericReturnType<K keyof SomeMap = any> = {
    title: K,
    params: SomeMap[K] extends Function ? ArgumentTypes<SomeMap[K]> : undefined,
};

And function written like

function ensureReturnTypeIsRight(...args: any[]): GenericReturnType {
    const shouldHaveParams = Math.random() > 0.5;

    if (shouldHaveParams) {
        const value: GenericReturnType<'prop2'> = {
            title: 'prop2',
            params: ['1', '2', '3'],
        };

        return value;
    }

    const value: GenericReturnType<'prop1'> = {
        title: 'prop1',
        params: undefined,
    };

    return value;
}

So TypeScript will check if it's possible to assign a value of following type to a variable. But is it possible to do so without generics?

I'm sorry for any unclear thoughts.

Upvotes: 1

Views: 247

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

What you are really looking for is a union taht should look something like:

type FnReturnType = {
    title: "prop1";
    params: undefined;
} | {
    title: "prop2";
    params: string[];
}

Such a union would ensure the return type is correct for a given title. Fortunately we can generate such a union from SomeMap using some mapped types.

type FnReturnType= {
    [K in keyof SomeMap] -?: { // Transform all properties of SomeMap to a type that is a member of our final union 
        title: K,
        params: SomeMap[K] extends (...args: any[]) => any ? Parameters<SomeMap[K]> : undefined,
    }
}[keyof SomeMap]; // get a union of all types in our previously generated type 

function ensureReturnTypeIsRight(...args: any[]): FnReturnType {
    const shouldHaveParams = Math.random() > 0.5;

    if (shouldHaveParams) {
        return {
            title: 'prop2',
            params: ['1', '2', '3'],
        };
    }

    return {
        title: 'prop1',
        params: undefined,
    };
}

Upvotes: 2

Related Questions