简体   繁体   中英

Type that depends on generic argument as value argument to function

I have a generic type Group that looks like this:

// K -> Key
// I -> Input
type Group<K, I> = {
    key: K;
    func: (i: I) => void;
};

There is a fixed number of Group values which I declared in an object like this:

const GROUPS = {
    "a": {
        func: (i: {x: number}) => { console.log(i); },
        key: "a",
    },
    "b": {
        func: (i: { y: number }) => { console.log(i) },
        key: "b"
    }
} as const;

I then have 2 utility types to refer to all the possible group keys and all the possible group inputs:

type GroupKey = keyof typeof GROUPS;
type GroupInput<K extends GroupKey> = Parameters<typeof GROUPS[K]["func"]>[0];

// GroupValue test:
type TestType = GroupInput<"b">; // { y: number}, this works

Finally, I have a function that receives both a group key and a group input:

function test<K extends GroupKey>(key: K, input: GroupInput<K>) {
    if (key === "b") {
        (input.y); // Why doesn't TypeScript understand that `input.y` must be `number` here?
    }
}

This function is generic over the type of the key that is passed in and unfortunately, TypeScript cannot "understand" that if key is "b" , then input is of type { y: number } . Why is this the case, what is TypeScript missing to be able to do this? I'd especially like to find a GitHub issue on this (so that I can subscribe to it), but I wasn't able to find one as this type of thing is particularly hard to search for.

Full Playground URL

Please consider this snippet:

const key = 'a' as GroupKey
const input = { y: 1 } // optionally cast as GroupInput<'b'> or as GroupInput<GroupKey>

test(key, input) // compiles, but not intended

The input might be independent from the key . There are no guarantees input.y must be a number when test is called with value 'b' as the first argument. type TestType = GroupInput<"b"> uses literal type ( 'b' ) which allows Typescript to restrict 'a' | 'b' 'a' | 'b' to just 'b' . The same applies for test('b', ...) , but passing key of type 'a' | 'b' 'a' | 'b' allows to pass input of type GroupInput<'a' | 'b'> GroupInput<'a' | 'b'> .

One option would be to check if 'y' in input , but this still does not address the main issue of not allowing wrong arguments to test . In general case input as GroupInput<'b'> is unsafe and should be avoided at any cost.

A possible fix:

type Params = { [K in GroupKey]: [key: K, input: GroupInput<K>] } // key: and input: are used for auto-completion instead of generic arg_0, arg_1

function test2(...args: Params[GroupKey]) {
    // const [key, input] = args // will not work
    if (args[0] === "b") {
        const input = args[1];
        input.y; // number
    }
}

test2('b', { y: 1 }) // ok
test2('b', { x: 1 }) // error
test2(key, input) // error

Playground

Because the input parameter is independent from your key parameter. input is not required to have y as a property although your key might be equal to 'b' . You would have to Typecast your input :

 function test<K extends GroupKey>(key: K, input: GroupInput<K>) {
    if (key === "b") {
        const typedInput = input as GroupInput<'b'>;
        (typedInput.y)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM