-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type manipulations: union to tuple #13298
Comments
|
You can already do [3, 1, 2][number] // => 1 | 2 | 3
type U<T extends any[], U = never> = T[number] | U
U<[3, 1, 2]> // => 1 | 2 | 3
U<[1], 2 | 3> // => 1 | 2 | 3 How about a concat operator for type U = 1 | 2 | 3
type T = [0] + U // => [0, 1, 2, 3]
type S = U + [0] // => [1, 2, 3, 0]
type R = [1] + [2] // => [1, 2]
type Q = R + R // => [1, 2, 1, 2]
type P = U + U // Error: cannot use concat operator without >=1 tuple
type O = [] + U + U // => [1, 2, 3, 1, 2, 3]
type N = [0] + any[] // => any[]
type M = [0] + string[] // Error: type '0' is not compatible with 'string'
type L = 'a' + 16 + 'z' // => 'a16z' Are there good use cases for preserving union order? (while still treating unions as sets for comparison purposes) |
I had used a conditional type for tuple to union: type ElementOf<T> = T extends (infer E)[] ? E : T; Works for both arrays and tuples. |
or just [1,2,3][number] will give you 1 | 2 | 3 |
Decided to stop being a lurker and start joining in the Typescript community alittle more hopefully this contribution helps put this Union -> Tuple problem to rest untill Typescript hopefully gives us some syntax sugar. This is my "N" depth Union -> Tuple Converter that maintains the order of the Union
|
Finally removed the bit about union to tuple, since there are plenty of ways to do that now (there weren’t when this suggestion was first made). Also, much thanks to @ShanonJackson, that looks awesome and I will have to try that. Still, that’s a lot of code for this; sugar would be rather appreciated here. Or at least a built-in type that comes with Typescript, so that doesn’t have to be re-implemented in every project. |
@krryan A solution of that size should be published as an NPM package, IMO. Worth noting: The |
@aleclarson Yes, but installing a dependency is, to my mind, still “reimplementing” it, at least in the context here. Sure, an NPM package is superior to copying and pasting that code around. But I don’t think either should be necessary for this. It’s a language construct that is broadly useful to all Typescript developers, in my opinion, so it should just be available (and quite possibly be implemented more easily within tsc than as a type in a library). Anyway, good point about the string limitation; that’s quite severe (I might still be able to use that but it’s going to take some work since I’ll have to get a tuple of my discriminants and then distribute those appropriately, but I think it will work for my purposes). |
I fear that while this solution works, it is very compiler unfriendly .. I added just a couple more keys to the object and when I hovered over it the language server got up to 100% CPU usage, ate up 3GB of RAM and no tooltips ever show up. interface Person {
firstName: string;
lastName: string;
dob: Date;
hasCats: false;
hasCats1: false;
hasCats2: false;
hasCats3: false;
hasCats4: false;
}
type Test = TupleOf<keyof Person> // tool tip never shows up HUGE amount of RAM and CPU Used, |
Sorry this has been stuck in Need Investigation so long! My primary question is: What would this be useful for? Hearing about use cases is really important; the suggestion as it stands seems like an XY problem situation. Secondary comments: This suggestion could almost certainly never happen; problems with it are many. First, union order is not something we can ever allow to be observable. Internally, unions are stored as a sorted list of types (this is the only efficient way to quickly determine relationships between them), and the sort key is an internal ID that's generated incrementally. The practical upshot of this is that two extremely similar programs can generate vastly different union orderings, and the same union observed in a language service context might have a different ordering than when observed in a commandline context, because the order in which types are created is simply the order in which they are checked. Second, there are basic identities which are very confusing to reason about. Is // K always has arity 2?
type K<T, U> = tupleof (T | U);
// Or Q has arity 3? Eh?
type Q = K<string, number | boolean>; There are more problems but the first is immediately fatal IMO. |
@RyanCavanaugh The primary use-case for me is to ensure complete coverage of all the types that a function claims to be able to handle in testing scenarios. There is no way to generate an array you can be sure (tsc will check) has every option. Order doesn’t matter to me at all, which makes it frustrating to have that as a fatal flaw. I think Typescript programmers are already familiar with union ordering being non-deterministic, and that’s never really been a problem. I wonder if creating something typed as As for |
@krryan Yes but the problem is that if type A = "A"
type B = "B"
type AB = A | B
function tuple(t: tupleof AB) {}
tuple(['A', 'B'])// Change the order in which A and B are declared and this becomes invalid .. very brittle ... |
Yes tuples have a strict order unless you write a implementation that can turn If people do care about the order (i don't) then i think just write a implementation that turns |
@dragomirtitian I fully understand that, which is why I suggested some alternative type that isn’t a tuple type to indicate that we are talking about an unordered set of exactly one each of every member of a union. Which it now dawns on me can be accomplished for strings by creating a type that uses every string in a union of strings as the properties of a type. For example: const tuple = <T extends unknown[]>(...a: T): T => a;
type ElementOf<T> = T extends Array<infer E> ? E : T extends ReadonlyArray<infer E> ? E : never;
type AreIdentical<A, B> = [A, B] extends [B, A] ? true : false;
type ObjectWithEveryMemberAsKeys<U extends string> = {
[K in U]: true;
};
const assertTupleContainsEvery = <Union extends string>() =>
<Tuple extends string[]>(
tuple: Tuple,
) =>
tuple as AreIdentical<
ObjectWithEveryMemberAsKeys<Union>,
ObjectWithEveryMemberAsKeys<ElementOf<Tuple>>
> extends true ? Tuple : never;
const foo = 'foo' as const;
const bar = 'bar' as const;
const baz = 'baz' as const;
const assertContainsFooBar = assertTupleContainsEvery<typeof foo | typeof bar>();
const testFooBar = assertContainsFooBar(tuple(foo, bar)); // correctly ['foo', 'bar']
const testBarFoo = assertContainsFooBar(tuple(bar, foo)); // correctly ['bar', 'foo']
const testFoo = assertContainsFooBar(tuple(foo)); // correctly never
const testFooBarBaz = assertContainsFooBar(tuple(foo, bar, baz)); // correctly never
const testFooBarBar = assertContainsFooBar(tuple(foo, bar, bar)); // incorrectly ['foo', 'bar', 'bar']; should be never There’s probably a way to fix the > extends true ? Tuple : {
missing: Exclude<Union, ElementOf<Tuple>>;
extra: Exclude<ElementOf<Tuple>, Union>;
}; though that potentially has the problem of a user thinking it’s not an error report but actually what the function returns, and trying to use This works for strings (and does not have the compiler problems that the suggestion by @ShanonJackson has), but doesn’t help non-string unions. Also, for that matter, my real-life use-case rather than Foo, Bar, Baz is getting |
How do tuples, as opposed to unions, help with this? I'm begging y'all, someone please provide a hypothetical code sample here for what you'd do with this feature so I can understand why 36 people upvoted it 😅
I can accept this at face value, but you have to recognize that it'd be a never-ending source of "bug" reports like this. The feature just looks broken out of the gate: type NS = tupleof number | string;
// Bug: This is *randomly* accepted or an error, depending on factors which
// can't even be explained without attaching a debugger to tsc
const n: NS = [10, ""]; I question whether it's even a tuple per se if you're not intending to test assignability to/from some array literal. |
I'm with @krryan -- only the latter makes sense here. The former would seem quite arbitrary.
This is perfectly, as this is not a blocker to its use-cases.
One big problem I see this as solving is map functions on objects (Lodash's
Anyone who has used Angular's state management library ngrx will be aware that getting type-safe state management for their front-end application involves horrific amounts of boilerplate. And in plain JavaScript, it has always been easy to imagine an alternative that is DRY. Type-safe edit: I think this depends on the boogieman |
I basically just want to be able to do this: const objFields: [['foo', 3], ['bar', true]] = entries({ foo: 3, bar: true }); I'm not sure whether the ES spec guarantees ordering of object entries or not. Node's implementation seems to, but if the spec doesn't, then this may just not be something TypeScript should facilitate (since it would be assuming a specific runtime). |
@treybrisbane the order is not guaranteed. What do you think of this? type Entries<K extends object> = {
[Key in keyof K]: [Key, K[Key]]
};
function entries<K extends object>(obj: K): Entries<K>[keyof K][] {
return Object.keys(obj).map(k => [k, obj[k]]) as any;
}
const objFields = entries({ foo: 3, bar: "x" });
for (const f of objFields) {
if (f[0] === "foo") {
console.log(f[1].toFixed());
} else if (f[0] === "bar") {
console.log(f[1].toLowerCase());
} else {
// Only typechecks if f[0] is exhausted
const n: never = f[1]
}
} |
@RyanCavanaugh when you say things about how order matters it makes me smile, please tell me where in the spec of typescript can i read about the order of overloads on the same method of the same interface coming from different *.d.ts files please, thank you |
Each overload is in the order that it appears in each declaration, but the ordering of the declarations is backwards of the source file order. That's it. |
and source file order is what? 🎥🍿😎 |
Quite the tangent from this thread! |
you people did it one time, you can do it again, order is order |
@jasonkuhrt This is likely a problem inherent in TypeScript due to the size of your union. It has a limit on the depth of recursive calculation. With @tatemz 's solution, 4.5 is allowing me something like 47 long string properties while only ~35 in 4.4. It's odd, but so is Typescript 😆 |
Documenting some findings. I originally found this thread because I was interested in taking a Union (e.g. That said, it turns out most of my use-cases can be solved by this answer by using a distributive conditional type. Example (playground link) type MyLiterals = "foo" | "bar";
type MyLiteralsMappedToObjects<T extends MyLiterals> = T extends never ? never : { value: MyLiteral }; |
I would like to find a solution for the following: type MyProps = {
booleanProp: boolean;
optionalDateProp?: Date;
};
const myJSONObject = {
"booleanProp": "false",
"optionalDateProp": "2021-11-25T12:00:00Z"
};
const coerce = (o: any): MyProps => /* ??? */
coerce(myJSONObject) /* = {
booleanProp: false,
optionalDateProp: new Date("2021-11-25T12:00:00Z")
} */ I wish for this operation, so I can implement // https://stackoverflow.com/a/66144780/11630268
type KeysWithValsOfType<T, V> = keyof {
[P in keyof T as T[P] extends V ? P : never]: P;
} &
keyof T;
type MyPropsOfTypeDate = KeysWithValsOfType<MyProps, Date> | KeysWithValsOfType<MyProps, Date | unknown>;
const myPropsOfTypeDate = new Set(/* ??? */);
type MyPropsOfTypeBoolean = KeysWithValsOfType<MyProps, boolean> | KeysWithValsOfType<MyProps, boolean | unknown>;
const myPropsOfTypeBoolean = new Set(/* ??? */);
type MyPropKeys = keyof MyProps;
const myPropKeys = new Set(/* ??? */);
const coerce = (o: any): MyProps => {
let p = {};
for (const k in myProps) {
if (myPropsOfTypeDate.has(k)) {
p[k] = new Date(o[k]);
} else if (myPropsOfTypeBoolean.has(k)) {
p[k] = o[k] === "true";
} else {
p[k] = o[k]
}
}
return p;
} How can I achieve this (or something comparable) without turning a union type of string literals into a runtime object ( |
@fdcds The typing of your This isn’t really the place to get into those better ways, but were it me, I would go with this: type MyJsonObject = {
[K in keyof MyProps]: MyProps[K] extends Date ? string : MyProps[K];
}
function coerce(props: MyProps): MyJsonObject {
result = {} as MyJsonObject;
Object.keys(props).forEach(key => {
result[key] = props[key] instanceof Date ? props[key].toISOString() : props[key];
});
return result;
} I just woke up, wrote this on my phone, and did not test it. If it doesn’t completely work, it should still be enough to point you in the right directions. Please do not clutter this thread, or even this issue tracker, with questions about it: questions like this belong on Stack Overflow. |
Here is how you can extend @tatemz solution to work for much larger unions by taking advantage of the fact that typescript uses tail-call for certain types of recursive types. This should work for unions of size up to 1000. (Though it gets slow fast.) type UnionToIntersection<U> = (
U extends never ? never : (arg: U) => never
) extends (arg: infer I) => void
? I
: never;
type UnionToTuple<T, A extends any[] = []> = UnionToIntersection<
T extends never ? never : (t: T) => T
> extends (_: never) => infer W
? UnionToTuple<Exclude<T, W>, [...A, W]>
: A; |
I would like to map a union to a tuple and then use this tuple to map it to an array where every original union type is used once as a key. Is that possible somehow? I tried this, but it fails: type SomeUnion = 'hello' | 'world';
type SomeTuple = UnionToTuple<SomeUnion>;
type Box<T> = { someField: string; type: T };
type Boxes = {
[Index in keyof SomeTuple]: Box<SomeTuple[Index]>
} & {length: SomeTuple['length']};
const boxes: Boxes = [
{ someField: '', type: 'hello' },
{ someField: '', type: 'world' },
]
// helper below:
// see https://github.com/microsoft/TypeScript/issues/13298#issuecomment-1610361208
type UnionToIntersection<U> = (U extends never ? never : (arg: U) => never) extends (arg: infer I) => void
? I
: never;
type UnionToTuple<T, A extends any[] = []> = UnionToIntersection<T extends never ? never : (t: T) => T> extends (_: never) => infer W
? UnionToTuple<Exclude<T, W>, [...A, W]>
: A; |
No, it is not, and basically can’t be made to be. Effectively, the problem is that unions do not have order, while arrays (and sets and so on) do. So to properly capture “each possibility of If you are talking about a union of strings (or numbers, or symbols I think?), there is a solution: you can use an object’s keys as an “order-less” “array” of sorts. (In truth, object keys do have an order defined by JavaScript but Typescript treats reorderings as the same object.) But for me, for the most part, I have tried to stick to defining the array first, and defining the union based on the array (which is easy). Kind of awkward, not my favorite, I just woke up so I can’t think of any but I feel like there are some limitations, but it mostly works. |
For those using Adding const asAllUnionTuple = <T>() => <const U extends ReadonlyArray<any>>(
cc: AllUnionTuple<T, U>
) => cc; The downside is that the resulting tuple is now |
This prevents the need of exporting BarSeries, LineSeries and ScatterSeries. This losses the type safety check that the TDatum type of CartesianSeries extends the ErrorBoundSeriesNodeDatum type. This introduces two risks: 1. If the BarSeries, LineSeries, ScatterSeries are changed such that the TDatum type no longer extends the ErrorBoundSeriesNodeDatum, then this will cause a bug regression. 2. If a new series types is added/modified with the intent of supporting error bars, then there will be runtime errors if the series does not respect the assumptions of errorBar.ts The first risk is acceptable because our tests should catch this. The second risk is acceptable because the developer writing this should notice the problem. Note: the AgErrorBarSupportedSeriesTypes constant is required because the errorBar.ts file needs a string[] to perform its checks. Converting a string[] to a union is trivial, but converting a union to a string[] is more complicated. See: microsoft/TypeScript#13298 (comment)
This prevents the need of exporting BarSeries, LineSeries and ScatterSeries. This losses the type safety check that the TDatum type of CartesianSeries extends the ErrorBoundSeriesNodeDatum type. This introduces two risks: 1. If the BarSeries, LineSeries, ScatterSeries are changed such that the TDatum type no longer extends the ErrorBoundSeriesNodeDatum, then this will cause a bug regression. 2. If a new series types is added/modified with the intent of supporting error bars, then there will be runtime errors if the series does not respect the assumptions of errorBar.ts The first risk is acceptable because our tests should catch this. The second risk is acceptable because the developer writing this should notice the problem. Note: the AgErrorBarSupportedSeriesTypes constant is required because the errorBar.ts file needs a string[] to perform its checks. Converting a string[] to a union is trivial, but converting a union to a string[] is more complicated. See: microsoft/TypeScript#13298 (comment)
I thought there might be a way to title case a word using an `Intl` function, but I couldn't find any. I also wanted to strictly check the value of my endian types mapped value array, but I'm not sure how you could make a tuple out of a union. I'd like to do `["big", "little"] satisfies tupleof Endian` essentially. This works nicely as well though. I also removed the demo Launch Handler manifest config from the last commit. microsoft/TypeScript#13298 https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript https://dev.to/zenulabidin/moment-js-vs-intl-object-4f8n https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript/60751434#60751434 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter https://www.geeksforgeeks.org/convert-string-to-title-case-in-javascript/
type MyTypeUnion = MyType1 | MyType2[]
type UnionToTuple = ...
type ConvertedTuple = UnionToTuple<MyTypeUnion>
type FirstType = ConvertedTuple[1] // MyType1
type SecondType = ConvertedTuple[2] // MyType2[] How can I do the |
You can’t, it’s not possible, that’s why this issue is closed. Unions are unordered, tuples are ordered—a tuple requires information that a union hasn’t got. If possible—it isn’t always—you may want to write your definition as an array to begin with, and then tuple-to-union is easy: export const tuple = [
// …
] as const;
export type Union = typeof tuple[number]; Unfortunately, this often isn’t workable when your union isn’t hard-coded, but derived from other types. There is a way you can have the compiler check that your tuple contains all the elements in the union, nothing else, and no duplicates, but it’s a fairly tedious amount of boilerplate: This bit you can re-use: /** The unique elements of the input tuple, in order. Only works with `readonly` tuples. */
export type SetTuple<T extends readonly any[]> = _SetTuple<T>;
type _SetTuple<T extends readonly any[], A extends readonly any[] = readonly []> =
T extends readonly [infer H, ...infer R]
? H extends A[number]
? _SetTuple<R, A>
: _SetTuple<R, readonly [...A, H]>
: A; Here are your union and tuple definitions, and compile-time testing that the tuples match the union. import { SetTuple } from 'set-tuple'; // wherever SetTuple is exported from
export type TheUnion = 'foo' | 'bar';
/** A correct tuple, in the same order as the union */
export const validTuple = ['foo', 'bar'] as const;
((): SetTuple<typeof validTuple> => validTuple); // no error: has no duplicates
((): readonly TheUnion[] => validTuple); // no error: does not include anything but 'foo' and 'bar'
((union: TheUnion): typeof validTuple[number] => union); // no error: includes both 'foo' and 'bar'
/** A correct tuple, in the opposite order as the union (doesn't matter) */
export const alsoValidTuple = ['bar', 'foo'] as const;
((): SetTuple<typeof alsoValidTuple> => alsoValidTuple); // no error: has no duplicates
((): readonly TheUnion[] => alsoValidTuple); // no error: does not include anything but 'foo' and 'bar'
((union: TheUnion): typeof alsoValidTuple[number] => union); // no error: includes both 'foo' and 'bar'
/** A wrong tuple, for several reasons */
export const invalidTuple = ['bar', 'bar', 'baz'] as const;
((): SetTuple<typeof invalidTuple> => invalidTuple); // error: duplicate 'bar' elements
((): readonly TheUnion[] => invalidTuple); // error: 'baz' is not in the union
((union: TheUnion): typeof invalidTuple[number] => union); // error: 'foo' is not in the tuple Here, we use a trio of never-invoked anonymous function expressions, which don’t pollute the namespace and have minimal effect on the run time, and allow us to include some statements that the compiler will check for us, so we can confirm the tuple is what we want it to be. As you can see, you have to include all three statements for each tuple you want to check. (You could combine the first two, technically, but that’s not a lot of savings and it makes the error messages harder to read.) I tried writing a utility type or even utility function that would save on this boilerplate, but everything I came up with had the same two flaws: you still needed a bunch of boilerplate to cause it to actually error when it’s supposed to, and the error messages are impossible to read. Still, if you only have a few such tuples, and they really must cover the union, this is a safe solution. |
type TuplifyUnion<U extends string> = {
[S in U]: // for each variant in the union
Exclude<U, S> extends never // remove it and..
? [S] // ..stop recursion if it was the last variant
: [...TuplifyUnion<Exclude<U, S>>, S] // ..recur if not
}[U] // extract all values from the object
type fs = TuplifyUnion<'1' | '2' | '3'>;
//equal to
type fs = ["3", "2", "1"] | ["2", "3", "1"] | ["3", "1", "2"] | ["1", "3", "2"] | ["2", "1", "3"] | ["1", "2", "3"] |
Ok, yes, that works, as long as the union is very small. I tested my implementation with a 114-member union (letters A-Z and a-z, digits 0-9, Greek letters Α-Ω and α-ω, and 'foo', 'bar', and 'baz'), which was no problem. In my testing, I also came up with a less-boilerplate-y way to create a re-usable type to check a tuple covers a union: export type TestTupleExactlyCoversUnion<T extends readonly any[], U extends string | number | bigint | boolean | null | undefined> =
[T, U] extends [SetTuple<T> & readonly U[], T[number]] ? true : Split<T extends any ? 'string--------------------------------------------' : never>;
type Split<T> = T extends `${infer H}${infer Rest}` ? readonly [H, ...Split<Rest>] : readonly [];
{
type Good = TestTupleExactlyCoversUnion<typeof validTuple, TheUnion>;
type Bad = TestTupleExactlyCoversUnion<typeof invalidTuple, TheUnion>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Type instantiation is excessively deep and possibly infinite. ts(2589)
} Putting the types in the brackets avoids polluting the namespace, so the names only have to be unique within that context, which is good. You still have to assign names to them, which is annoying. The error message is also much less useful: you just get “Type instantiation is excessively deep and possibly infinite,” which is not terribly helpful (also, if you have a union too large for |
For anyone needing to do enum ETypeKey {
Null = 'null',
String = 'string',
Number = 'number',
Boolean = 'boolean',
Symbol = 'symbol',
Date = 'date'
}
type TMap<T> = T extends null
? typeof ETypeKey.Null
: T extends string
? ETypeKey.String
: T extends number
? ETypeKey.Number
: T extends boolean
? ETypeKey.Boolean
: T extends symbol
? ETypeKey.Symbol
: T extends Date
? ETypeKey.Date
: never;
type TReverseMap<TKey> = TKey extends ETypeKey.Null
? null
: TKey extends ETypeKey.String
? string
: TKey extends ETypeKey.Number
? number
: TKey extends ETypeKey.Boolean
? boolean
: TKey extends ETypeKey.Symbol
? symbol
: TKey extends ETypeKey.Date
? Date
: never;
type TUnionToKeys<T> = T extends unknown ? TMap<T> : never;
// SEE: https://github.com/microsoft/TypeScript/issues/13298#issuecomment-2373724144
type TKeysToTuple<T extends string> = {
[k in T]: Exclude<T, k> extends never // FOR EACH VARIANT IN THE UNION // REMOVE IT AND...
? [k] // ...STOP RECURSION IF IT WAS THE LAST VARIANT
: [...TKeysToTuple<Exclude<T, k>>, k]; // ...RECURSE IF NOT
}[T]; // EXTRACT ALL VALUES FROM THE OBJECT
type TUnmappedTuple<T extends readonly unknown[]> = {
[K in keyof T]: TReverseMap<T[K]>;
};
type TRevert<T> = T extends unknown[] ? TUnmappedTuple<T> : never;
type TUnionToTuple<T> = TRevert<TKeysToTuple<TUnionToKeys<T>>>;
type TTest1 = TUnionToTuple<number | string>; // [number, string] | [string, number]
type TTest2 = TUnionToTuple<boolean>; // [boolean]
type TTest3 = TUnionToTuple<number | string | Date>; // [Date, number, string] | [number, Date, string] | [Date, string, number] | [string, Date, number] | [number, string, Date] | [string, number, Date] What @rtritto wanted to do, should be possible with this, as long as we are talking primitives. |
A suggestion to create a runtime array of union members was deemed out of scope because it would not leave the type system fully erasable (and because it wouldn't be runtime complete, though that wasn't desired). This suggestion is basically a variant of that one that stays entirely within the type domain, and thus stays erasable.
The suggestion is for a keyword similar to
keyof
that, when given a union type, would result in a tuple type that includes each possibility in the union.Combined with the suggestion in this comment to instead implement a codefix to create the array literal, this could be used to ensure that 1. the array was created correctly to begin with, and 2. that any changes to the union cause an error requiring the literal array to be updated. This allows creating test cases that cover every possibility for a union.
Syntax might be like this:
Some issues I foresee:
I don't know what ordering is best (or even feasible), but it would have to be nailed down in some predictable form.
Nesting is complicated.
I expect generics would be difficult to support?
Inner unions would have to be left alone, which is somewhat awkward. That is, it would not be reasonable to turn
Wrapper<Foo|Bar>
into[Wrapper<Foo>, Wrapper<Bar>]
even though that might (sometimes?) be desirable. In some cases, it’s possible to use conditional types to produce that distribution, though it has to be tailored to the particularWrapper
. Some way of converting back and forth betweenWrapper<Foo|Bar>
andWrapper<Foo>|Wrapper<Bar>
would be nice but beyond the scope of this suggestion (and would probably require higher-order types to be a thing).My naming suggestions are weak, particularly
tupleof
.NOTE: This suggestion originally also included having a way of converting a tuple to a union. That suggestion has been removed since there are now ample ways to accomplish that. My preference is with conditional types and
infer
, e.g.ElementOf<A extends unknown[]> = A extends (infer T)[] ? T : never;
.The text was updated successfully, but these errors were encountered: