-
Notifications
You must be signed in to change notification settings - Fork 16
OT - union type to array or infer how many types are in the union #80
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
Comments
I must admit, there are some scary things going on in here. But I gave it a shot, we'll see if it is useful to you. I can't seem to find a way to send a union to an array or tuple, but there is an open discussion about it in the TS repo: microsoft/TypeScript#13298. The typescript team seems to be making headway on building up the tuple type, so I could see something like this making its way in at some point. From what I gather, one other way to solve your problem would be to create a utility that can count the number of keys in an object? For instance: interface Car {
tires: number;
doors: string;
engine: object;
}
type numKeys = CountKeys<Car>; // 3 To do this, I relied on this stackoverflow question: https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type It came out to this: interface I {
hi: number;
there: string;
friend: string;
}
// takes an object, and returns its values in an intersection
type IntersectionOfValues<T> =
{ [K in keyof T]: (p: T[K]) => void } extends
{ [n: string]: (p: infer I) => void }
? I
: never;
// example:
type a = IntersectionOfValues<I>; // => number & string & string
// which simplifies to // => number & string
// takes the first argument of each function in the intersection and puts it into the tuple
// order is not guaranteed, so this isn't a very useful type outside of this context
type IntersectionOfFunctionsToTuple<F> =
F extends {
(a: infer A): void;
(b: infer B): void;
(c: infer C): void;
} ? [A, B, C] :
F extends {
(a: infer A): void;
(b: infer B): void;
} ? [A, B] :
F extends {
(a: infer A): void
} ? [A] :
never;
type ToTuple<T> =
// pass the intersection of these functions to create the tuple type
// store the keys as arguments to functions so that they can be retrieved with inference later
IntersectionOfFunctionsToTuple<
// convert each key into a function that takes that key type as an argument
IntersectionOfValues<{ [K in keyof T]: (v: K) => void }>
>;
type CountKeys<T> = ToTuple<T>['length'];
type numKeys = CountKeys<I>; It is too bad that it would require adding more cases to Hopefully, this helps! I'm glad simplytyped has been useful for you :) |
Thank you very much! , yes It's not trouble to add more cases manually to IntersectionOfFunctionsToTuple, and I also feel that something is scary about this. But I really want to validate that, not only the values of an array argument are correct, but also its length. THanks again!!! keep it up ! |
No problem! Really glad I was able to help. Hopefully, typescript will make it possible to come up with a more elegant solution to some of these with time. The language has been growing and improving so quickly, I imagine a better solution will be possible in the not too distant future. |
I agree and although I'm not an expert on many strongly typed languages, I wonder if others allow API authors to be as declarative as you can with TypeScript! I also envision great future not only in this area but also on language service plugins, compiler API, transformations, etc. The future will be interesting... Thanks! |
// union to intersection of functions
type UnionToIoF<U> =
(U extends any ? (k: (x: U) => void) => void : never) extends
((k: infer I) => void) ? I : never
// return last element from Union
type UnionPop<U> = UnionToIoF<U> extends { (a: infer A): void; } ? A : never;
// prepend an element to a tuple.
type Prepend<U, T extends any[]> =
((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;
type UnionToTupleRecursively<Union, Result extends any[]> = {
1: Result;
0: UnionToTupleRecursively_<Union, UnionPop<Union>, Result>;
// 0: UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
}[[Union] extends [never] ? 1 : 0];
type UnionToTupleRecursively_<Union, Element, Result extends any[]> =
UnionToTupleRecursively<Exclude<Union, Element>, Prepend<Element, Result>>;
type UnionToTuple<U> = UnionToTupleRecursively<U, []>;
// support union size of 43 at most
type Union43 = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 | 43;
type Tuple = UnionToTuple<Union43>; @andnp I made some improvements so that it can work with union of any type, also made it work with larger union. BTW, in your original IntersectionOfFunctionsToTuple, more conditional types make no differences with single one, it always results empty interface if the intersection doesn't fit in, |
@fightingcat that's awesome. Do you know if it's possible not to enforce the union order in the Tuples ? |
@fightingcat How is that doesn't fail to compile since // union to intersection of functions
type UnionToIoF<U> =
(U extends any ? (k: (x: U) => void) => void : never) extends
((k: infer I) => void) ? I : never
// return last element from Union
type UnionPop<U> = UnionToIoF<U> extends { (a: infer A): void; } ? A : never;
// prepend an element to a tuple.
type Prepend<U, T extends any[]> =
((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;
type UnionToTupleRecursively<Union, Result extends any[]> = {
1: Result;
0: UnionToTupleRecursively_<Union, UnionPop<Union>, Result>;
// 0: UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
}[[Union] extends [never] ? 1 : 0];
type UnionToTupleRecursively_<Union, Element, Result extends any[]> =
UnionToTupleRecursively<Exclude<Union, Element>, Prepend<Element, Result>>;
type UnionToTupleOrdered<U> = UnionToTupleRecursively<U, []>;
// /** return an fixed length array with item type TItem */
type Tuple<TItem, TLength extends number> = [TItem, ...TItem[]] & {
length: TLength;
};
type UnionCount<T>=UnionToTupleOrdered<T>['length']
type UnionToTuple<T, L extends number> = Tuple<T, UnionCount<T>>
type UserTuple = UnionToTuple<1|2, 2>
var c : UserTuple = [1, 2] |
@cancerberoSgx I'm guessing, it caused ts server into infinte recursion while checking constrain for Right now I have no idea how to fix it, I think it's a bug of type checker. |
I think it's a TS issue failing to verify type declaration referencing itself in that particular case. And your solution is exploiting that issue. But just a guess. @andnp BTW I'm also a collection of some type helpers (not as advanced as yours) and I wanted to test these, but instead using a compile-time check like you are doing (or tsd-check) does, I wanted to known if it's possible to do it at run time, by compiling the code at run time (using the same project configuration). This way I'm able to check compile error situations. For example: describe('particular problem tsd-check-runtime is good at', ()=>{
it('Tuple wont verify out of range access', ()=>{
expect(`
declare var a: Tuple<number, 2>
var c = a[3]
`).toCompile()
})
it('ArrayLiteral will verify out of range access', ()=>{
expect(`
declare var a: ArrayLiteral<number, 2>
var c = a[3]
`).not.toCompile()
})
})
/* the types are:
export type Tuple<TItem, TLength extends number> = [TItem, ...TItem[]] & {
length: TLength
}
export type ArrayLiteral<T, L> = 1 extends L ? [] :1 extends L ? [T] : 2 extends L ? [T, T] : 3 extends L ? [T, T, T] : 4 extends L ? [T, T, T, T]: 5 extends L ? [T, T, T, T, T] : 6 extends L ? [T, T, T, T, T, T]: 7 extends L ? [T, T, T, T, T, T, T]: 8 extends L ? [T, T, T, T, T, T, T, T]: 9 extends L ? [T, T, T, T, T, T, T, T, T]: never
*/ Wonder if you know a way of perform these kind of checking without having to compile at runtime ? Also wanted to share the tool with you since perhaps could be useful in this project. The tool: https://github.com/cancerberoSgx/tsd-check-runtime Would love your thoughts about this. Thanks!, keep it up |
extremely late contribution but I believe I've found someone else's great solution to this that's way simpler. originally posted betanii https://old.reddit.com/r/typescript/comments/w72oja/union_permutation_object_key_count_union_cases/, which links to https://gist.github.com/betafcc/f9084dd9f42ffea59ad6f2ff366b864f, which contains this code:
|
Sorry for this OT, but I'm struggling with this problem. Do you know if it's possible to get amount of a mapped object key or, in other words, the amount of types in an union type ?
I want to check against an array of a certain length. The length is the number of keys of some mapped object but is dynamic (could be any property value of something like:
interface I {
foo: {a:number}
bar: {g: boolean}
}
These types are autogenerated by a tool from an API specification. The user defines the key when the function is called f('foo', ...) so it's unknown at compile time.
So far I'm able to validate members of the input array, but I would also like to validate it's length in some cases. So I need a way to get the number of keys in a mapped object or the number of types in an union type (since it seems object keys type are always represented with union types)
// I want to make sure arg0 is an array of length equals to the number of keys of I[T] (given)
function f(type: T, arg0: Tuple<T, UnionCount>)
type Tuple<TItem, TLength extends number> = [TItem, ...TItem[]] & { length: TLength };
type ValueOfStringKey<T extends { [k: string]: any }, K extends string> = T[K];
In that case, do you know if it's possible to implement the UnionCount helper ? You have the opposite (array to union) in your library so I though perhaps you have a clue.
Thanks in advanced, BTW your library is awesome. Thanks
The text was updated successfully, but these errors were encountered: