Home > Back-end >  Combinations of a union type
Combinations of a union type

Time:10-01

I can generate combinations of unions that hold only property keys like this:

type KeyCombos<T extends PropertyKey> = {
  [K in T]: [K] | (KeyCombos<Exclude<T, K>> extends infer U extends any[] ? U extends U ? [K | U[number]] : never : never);
}[T];

and that is because mapped types only allow property keys. Here is some example usage of this type (wrapping in tuples so they don't get simplified into the original union):

type S = KeyCombos<"a" | "b" | "c">;
//   ^? ["a"] | ["b"] | ["c"] | ["b" | "c"] | ["a" | "b"] | ["a" | "c"] | ["a" | "b" | "c"]
type N = KeyCombos<1 | 2 | 3>;
//   ^? [1] | [2] | [3] | [2 | 3] | [1 | 2] | [1 | 3] | [1 | 2 | 3]

Now what I'd like to do is make this work for any kind of union. I thought I could use distributive conditionals, but either it's just not possible like this, or I'm using distributive conditionals incorrectly. Attempt 1 is show below:

type KeyCombos<T extends PropertyKey> = T extends T ? [T] | (KeyCombos<Exclude<T, T>> extends infer U extends any[] ? U extends U ? [T | U[number]] : never : never) : never

Obviously, Exclude<T, T> will be simplified to never, so this is not going to work. I get ["a"] | ["b"] | ["c"] and [1] | [2] | [3] for the examples above, respectively. I thought I could just "copy" T and distribute over that and fix this. Attempt 2 now:

type KeyCombos<T extends PropertyKey> = T extends infer K ? K extends K ? [K] | (KeyCombos<Exclude<T, K>> extends infer U extends any[] ? U extends U ? [K | U[number]] : never : never) : never : never

but this results in the same results as attempt 1.

How can I make this work for any union? Note that I don't want permutations with tuples. That's pretty manageable and I have found answers for that. I have seen Typescript string literal with permutations of union but no repetitions and How to declare a type as all possible combination of a union type? but I got "scammed" in the latter question since the answer did not really make combinations :(

Playground

P.S. If there was a question with an answer to this that flew under my radar, make sure to link it and I'll close this question as a duplicate

P.S.S. No, this isn't some gimmick for typings in a project. It's just having fun :)

CodePudding user response:

Using distributive conditionals is the right way. But as you noted, the Exclude is making problems. In both of your attempts, you are distributing over T which makes using Exclude with T useless. What you can do instead is copy T into an additional optional generic parameter O and then use O in the Exclude.

type KeyCombos<T, O = T> = 
  T extends infer U 
    ? [T] | (KeyCombos<Exclude<O, U>> extends infer U extends any[] 
               ? U extends U ? [T | U[number]] : never 
               : never)
    : never

Playground

  • Related