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 :(
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