Home > Enterprise >  How to associate the type of a function parameter with a certain enum value?
How to associate the type of a function parameter with a certain enum value?

Time:04-25

I would like to be able to associate an enumeration and a type when compiling in TypeScript.

The goal to fix the param of a method with an enum. I try to do that with a closure:

enum EnumType {
    TYPE1,
    TYPE2
}

interface Type1 {
    name: string;
}

interface Type2 {
    value: number;
}


function closure(tEnum: EnumType) {
    switch(tEnum){
        case EnumType.TYPE1: 
        return (t: Type1) => { console.log(t); }
        case EnumType.TYPE2: 
        return (t: Type2) => { console.log(t); }
    }
}

const foo1 = closure(EnumType.TYPE1);
foo1({ name: 'test'}); // here no error Type1 (but there is an error)
foo1({ value: 2}); // here error because not Type1

Playground

How do I get this example to work or is there an alternative?

CodePudding user response:

This is a fairly classic place for using function overloads:

function closure(tEnum: EnumType.TYPE1): (arg: Type1) => void;
function closure(tEnum: EnumType.TYPE2): (arg: Type2) => void;
function closure(tEnum: EnumType) {
    switch(tEnum){
        case EnumType.TYPE1: 
            return (t: Type1) => { console.log(t); }
        case EnumType.TYPE2: 
            return (t: Type2) => { console.log(t); }
        default:
            throw new Error(`Invalid enum value ${tEnum}`);
    }
}

Playground link

Note that only the first two signatures are public; the third is just an implementation signature other code doesn't see.

CodePudding user response:

As an alternative to function overloads, you can make the association in a utility type and then use generic type parameters to correlate the type of tEnum parameter and the case chosen:

enum EnumType {
    TYPE1,
    TYPE2
}

interface Type1 {
    name: string;
}

interface Type2 {
    value: number;
}

type TypeMap = {
    [EnumType.TYPE1]: Type1;
    [EnumType.TYPE2]: Type2;
};

function closure<T extends EnumType>(tEnum: T): (t:TypeMap[T]) => void {
    switch(tEnum){
        case EnumType.TYPE1: 
        return (t: TypeMap[T]) => { console.log(t); }
        case EnumType.TYPE2: 
        return (t: TypeMap[T]) => { console.log(t); }
    }

    throw new Error(`${tEnum} is not a type`);
}

const foo1 = closure(EnumType.TYPE1);
foo1({ name: 'test'}); // OK
foo1({ value: 2}); // error, OK

const foo2 = closure(EnumType.TYPE2);
foo2({ name: "test" }); // error, OK
foo2({ value: 2 }); // OK

Playground


As per discussion with jcalz, it is worth noting that the above example is insufficient if you access properties on t inside the closure function body as, by virtue of T being unresolved yet, TypeMap[T] is inferred to be a union Type1 | Type2, which means you will not be able to access properties that are not shared by both interfaces there.

An easy solution to this is to drop the switch in favour of a plain object matching the TypeMap via a mapped type:

function closure<T extends EnumType>(tEnum: T) {
    const map: { [P in keyof TypeMap]: (t: TypeMap[P]) => void } = {
        [EnumType.TYPE1]: (t) => { console.log(t.name); },
        [EnumType.TYPE2]: (t) => { console.log(t.value); }
    };

    return map[tEnum];
}

Playground

  • Related