Nick Latkovich
Nick Latkovich

Reputation: 125

Array of generics

I declare enum and its transformer:

enum E { A, B };
type TRANSFORM<T extends E> = T extends E.A ? E.B : E.A;

And declare array type with two elements, where the second one is transformed type of the first element:

type qwe<T extends E> = [T, TRANSFORM<T>];

It's works fine, when i provide special enum element:

const element: qwe<E.A> = [E.A, E.A]; // Ok: Error: (Type 'E.A' is not assignable to type 'E.B')

How can i declare an array of this elements? The only way i found is:

const arr: (qwe<E.A> | qwe<E.B>)[] = [[E.A, E.A], [E.B, E.A]];
// Ok: Error: (Type '[E.A, E.A]' is not assignable to type '[E.A, E.B] | [E.B, E.A]')

But typing as

type b = qwe<E.A> | qwe<E.B>;

May be too large, when enum E will contains more elements. How can i write this in short?

I was trying to declare like this:

type b = qwe<E>;

But it works not as expected. For example this array is correct:

const arr: qwe<E>[] = [[E.A, E.A], [E.B, E.A]];

Upvotes: 0

Views: 80

Answers (1)

jcalz
jcalz

Reputation: 327624

Before I get started I'm going to change your type names to be more conventional (i.e., qweQwe and TRANSFORMTransform). I'm also going to change Transform to be a lookup type instead of a conditional type:

type Transform<T extends E> = { [E.A]: E.B; [E.B]: E.A }[T];

This definition should evaluate to the same types as your intended use cases, (Transform<E.A> is E.B, Transform<E.B> is E.A, and Transform<E> is E), but the compiler will generally have an easier time making inferences about lookup types than it does about generic types. If you want you can change back to your original definition and the following should still work.


So, the solution I'd advise here is to make your Qwe<T> type distribute over the union operation, so that Qwe<A | B> will always evaluate to Qwe<A> | Qwe<B>. TypeScript lets you do this via distributive conditional types, of the form T extends U ? X : Y when T is an unresolved generic type parameter. For example:

type Qwe<T extends E = E> = T extends any ? [T, Transform<T>] : never;

The T extends any ? part looks like a no-op, but it has the important effect of preventing Qwe<E> from evaluating to [E, E]... instead, it will become [E.A, E.B] | [E.B, E.A] as you desired.

Also I added a type parameter default so that when you write the type as just Qwe it will be interpreted as Qwe<E>... since I imagine that's the type you will want to use most often.

Okay, let's make sure this works as you desired:

const arr: Qwe[] = [[E.A, E.B], [E.B, E.A]]; // okay
const bad: Qwe[] = [[E.A, E.A]]; // error

Looks good to me. Hope that helps; good luck!

Link to code

Upvotes: 2

Related Questions