Shepmaster
Shepmaster

Reputation: 430921

How can I write a generic function that uses the type of an enum member instead of the enum itself?

The TypeScript documentation on enums says:

enum members also become types as well!

I would like to have a function that takes a generic value that is an enum member and uses the specific enum member's type.

I see that I can use the enum member as a type:

enum Kind {
    Apple,
    Banana,
}

type Example1 = Kind.Apple;

I can use the enum member in a generic type context:

interface Wrapper<K> {
    kind: K;
}

type Example2 = Wrapper<Kind.Apple>;

When I pass the enum member to a function, it loses the fact that it's a specific enum member and instead makes it the parent's enum type:

const returned = <K>(kind: K): Wrapper<K> => ({
    kind,
})

const theValue = returned(Kind.Apple);

// Type 'Kind' is not assignable to type 'Kind.Apple'.
const butNot: Example2 = theValue;

Playground

Upvotes: 2

Views: 1742

Answers (2)

Shepmaster
Shepmaster

Reputation: 430921

You can explicitly specify what the generic type should be on the call to returned:

enum Kind {
    Apple,
    Banana,
}

interface Wrapper<K> {
    kind: K;
}

const returned = <K>(kind: K): Wrapper<K> => ({
    kind,
})

const theValue = returned<Kind.Apple>(Kind.Apple);

const thisWorksNow: Wrapper<Kind.Apple> = theValue;

Upvotes: 0

artem
artem

Reputation: 51629

The reason why kind parameter type is widened from Kind.Apple to Kind is spelled out in https://github.com/Microsoft/TypeScript/pull/10676 :

During type argument inference for a call expression the type inferred for a type parameter T is widened to its widened literal type if:

  • all inferences for T were made to top-level occurrences of T within the particular parameter type, and
  • T has no constraint or its constraint does not include primitive or literal types, and
  • T was fixed during inference or T does not occur at top-level in the return type.

If, as mentioned in the second bullet, you add constraint for generic parameter T it will start inferring specific literal type. The price for that is that it makes returned applicable to subtypes of Kind only.

enum Kind {
    Apple,
    Banana,
}

interface Wrapper<K> {
    kind: K;
}

const returned = <K extends Kind>(kind: K): Wrapper<K> => ({
    kind,
})

const theValue = returned(Kind.Apple);

const thisWorksNow: Wrapper<Kind.Apple> = theValue;

Upvotes: 3

Related Questions