typescriptgenericsenums

Mapped types doesn't fit the restriction of types `Type "" is not assignable to type 'never'`


I have played with typescript generics recently and found the behaviour that i'm not sure how to explain, on the begging i have:

Enum list with initial values
Couple of classes that present results, one class correspond exactly one enum value

So the task is write the function which maps initial values to result values (simple switch case) but it wasn't enough for me, because types is this case is something like:

1 enum => any of the result classes

so i proceeded with generics, create type map of initial enums to result values, made generic function, which works fine in this case, type is fine now:

1 enum => 1 result class

but the problem is that typescript don't want to recognise types of returned values in this case so i cast types to something like any, there is a minimal playground and the code if link expired:

enum TestKind {
    String = 'str',
    Number = 'num',
    Boolean = 'bool',
}

type TestKindTypeMap = {
    [TestKind.String]: string;
    [TestKind.Number]: number;
    [TestKind.Boolean]: boolean;
};

function transformerWithBadTyping(input: TestKind): TestKindTypeMap[TestKind] {
    switch (input) {
        case TestKind.String:
            return '';
        case TestKind.Number:
            return 0;
        case TestKind.Boolean:
            return false;
        default:
            throw new Error();
    }
}

console.log(transformerWithBadTyping(TestKind.String))
console.log(transformerWithBadTyping(TestKind.Number))
console.log(transformerWithBadTyping(TestKind.Boolean))

function transformer<K extends TestKind>(input: K): TestKindTypeMap[K] {
    switch (input) {
        case TestKind.String:
            return '';
        case TestKind.Number:
            return 0 as any;
        case TestKind.Boolean:
            return false as TestKindTypeMap[K];
        default:
            throw new Error();
    }
}

console.log(transformer(TestKind.String))
console.log(transformer(TestKind.Number))
console.log(transformer(TestKind.Boolean))

so the questions are:

  1. what's wrong with my code/ts and why i shall cast types to TestKindTypeMap[K] or any in this case of generic function? why typescript can't proceed without my help?
  2. is there any other ways how i can achieve strict typing?

Solution

  • Typescript cannot narrow a type which comes from a generic parameter, a solution to workaround would be to recreate the type map in the value space and return one of its members:

    Playground

    function transformer<K extends TestKind>(input: K): TestKindTypeMap[K] {
    
        const map = {
            [TestKind.String]: '',
            [TestKind.Number]: 0,
            [TestKind.Boolean]: false
        };
    
        return map[input];
    
    }