circular type conditional type
Currently, Last1
is valid, but seemingly-equivalent Last2
is not:
type Head<T extends any[]> = T extends [infer X, ...any[]] ? X : never;
type Tail<T extends any[]> =
((...x: T) => void) extends ((x: any, ...xs: infer XS) => void) ? XS : never;
type Last1<T extends any[]> = {
0: never,
1: Head<T>,
2: Last1<Tail<T>>,
}[T extends [] ? 0 : T extends [any] ? 1 : 2];
type Last2<T extends any[]> =
T extends [] ? never :
T extends [infer R] ? R :
Last2<Tail<T>>;
My suggestion is to make Last2
valid, since the recursion isn't necessarily unbounded.
It'd make recursive conditional types a lot easier and more intuitive to write. It'd also eliminate the need to guard against impossible cases when using the workaround like when using Last1
above. If you wanted to mandate termination, all I'd want is direct recursion - this makes it much easier to check for infinite recursion. (Other types are generally assumed to terminate, so you're only assessing control flow.)
See above in the suggestion summary. Here's a concrete example of how this file (with dependency) would be simplified:
type Last<L extends any[], D = never> =
L extends [] ? D :
L extends [infer H] ? H :
((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> :
D;
type Append<T extends any[], H> =
((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never;
type Reverse<L extends any[], R extends any[] = []> =
((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ?
Reverse<T, Append<R, H>> :
R;
type Compose<L extends any[], V, R extends any[] = []> =
((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ?
Compose<T, H, Append<R, (x: V) => H>> :
R;
export type PipeFunc<T extends any[], V> =
(...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>);
Currently, you could achieve similar via this, but as you can see, it's highly repetitive and boilerplatey:
type Last<L extends any[], D = never> = {
0: D,
1: L extends [infer H] ? H : never,
2: ((...l: L) => any) extends ((h: any, ...t: infer T) => any) ? Last<T> : D,
}[L extends [] ? 0 : L extends [any] ? 1 : 2];
type Append<T extends any[], H> =
((h: H, ...t: T) => any) extends ((...l: infer L) => any) ? L : never;
type Reverse<L extends any[], R extends any[] = []> = {
0: R,
1: ((...l: L) => any) extends ((h: infer H, ...t: infer T) => any) ?
Reverse<T, Append<R, H>> :
never,
}[L extends [any, ...any[]] ? 1 : 0];
type Compose<L extends any[], V, R extends any[] = []> = {
0: R,
1: ((...l: L) => any) extends ((a: infer H, ...t: infer T) => any) ?
Compose<T, H, Append<R, (x: V) => H>>
: never,
}[L extends [any, ...any[]] ? 1 : 0];
export type PipeFunc<T extends any[], V> =
(...f: Reverse<Compose<T, V>>) => ((x: V) => Last<T, V>);
(The original code snippet is linked to from here as a possible solution to the issue of _.compose
, _.flow
, and friends.)
My suggestion meets these guidelines:
BTW, here's the Wikipedia article on total functional programming, where TS's type system models to a very large extent in my experience (probably unintentionally). And yes, this could enable a type-encoded quicksort across integers and potentially strings if they were made comparable, without even requiring the type system to remain Turing-complete.
After including this, you could then permanently break the Turing-complete aspect by making a similar check with indexed types. Note that AFAICT, the Turing-complete nature is dependent on indexed types not being checked for recursive cases. It may require support for corecursive functions, depending on how much code breaks.
Oh, and another reason to address Turing completeness and potentially kill it is because the original "solution" to variadic composition happens to hang when plugged into the playground, due to a bug not very clear when just looking. It maxes out my computer's CPU without taking much memory at all, which is a pretty obvious problem, but it can happen when a type system is Turing-complete, but not sound enough to force you to be explicit about your intent (you see similar with C-like procedural languages and infinite loops with non-trivial logic, hence various control-flow-sensitive lints for it).
(Moving the conversation from #5453)
Regarding the Last1<>
implementation, the official word is something like "don't do it". @ahejlsberg said:
It's clever, but it definitely pushes things well beyond their intended use. While it may work for small examples, it will scale horribly. Resolving those deeply recursive types consumes a lot of time and resources and might in the future run afoul of the recursion governors we have in the checker.
Don't do it!
I would be enormously in favor of something that lets me express more recursive type functions without running afoul of the current pitfalls (warnings, hanging compiler, crashing compiler). Not sure how to get there from here, though. I suspect if the circularity warning were simply removed, the Last2<>
implementation would bog down the compiler in the same way that Last1<>
does. Assuming that the type checker sometimes treats T extends U ? X : Y
like X | Y
for testing assignability, any recursion that depends on conditional types to express termination could be a problem.
I'm not holding my breath for any solution involving a significant overhaul of the type checker. Can we think of some addition which wouldn't break existing code but would allow some (bounded) type function iteration/recursion?
Copying from the Design Notes for ease of linking.
@DanielRosenwasser said:
Recursive Types
- The intuition is not possible to form object graphs with circularities unless you defer somehow (either via laziness or state).
- Really there's no way to know if any arbitrary type will terminate.
- We can have limited types of recursion in the compiler, but the problem isn't whether or not the type terminates, it's how computationally intensive and memory heavy the resolution is.
- We have a meta-question: do we want people writing code like this?
- The motivating scenarios that people have are legitimate, but achieving them with these types is not necessarily going to work well for library consumers.
- Conclusion: we're not quite ready for this sort of thing.
May I add another compelling use case: you could lift the similar constraints on unions, intersections, and generic type parameters to address the possible results of JSON.parse(string)
without a reviver:
type JSONResult = null | string | boolean | number | Record<string, JSONResult> | JSONResult[];
In this case, JSONResult
can only really be these types, and anything else is entirely unreasonable. You can detect the safety and legality by ensuring that any cycles are abstracted through an object property of some kind. In the case of Record<string, JSONResult>
, it's a [key: string]
property, and in the case of JSONResult[]
, it's a [key: number]
property.
Edit: Missed a type.
type Last<T extends any[]> = T[Tail<T>['length']];
cross-linking #30188
And calling @pirix-gh... I think since you are using recursive conditional types inside ts-toolbelt and committed them into the typings for ramda it would be useful for someone to either reiterate that this is a Bad Idea, or give some update if there is one.
And if there ever is an update here past "don't do it" and "we're not ready for this", I'd expect to see some recursive conditional types included in the baseline tests for the language itself, so that future releases won't break them.
Quotes from https://github.com/microsoft/TypeScript/issues/5453#issuecomment-506847921
Can we discuss whether we can have official approval for the types provided by the ts-toolbelt or not?
Here's how the story started:
Performance
I created a heavy test that shows the Minus
type in action. It performs the Minus
216000 times in less than 4 seconds. This shows that TS can handle recursive types very well. But this is quite recent.
Since when?
This is thanks to Anders :tada: (#30769). He allowed me to switch from conditional types to indexed conditions (like a switch). And as a matter of fact, it improved performance by x6 (with everything being indexed).
Is it safe?
The lib also makes recursion safe with Iteration
that will prevent any overflow from TypeScript. In other words, if I
goes over 40 then it overflows and Pos<I>
equals number
. Thus stopping the recursion safely. Here's an example:
import {O, I, T} from 'ts-toolbelt'
// It works with the same principles `Minus` uses
type Assign<O extends object, Os extends object[], I extends I.Iteration = I.IterationOf<'0'>> = {
0: Assign<O.Merge<Os[I.Pos<I>], O>, Os, I.Next<I>>
1: O
}[
I.Pos<I> extends T.Length<Os>
? 1
: 0
]
type test0 = Assign<{i: number}, [
{a: '1', b: '0'},
{a: '2'},
{a: '3', c: '4'},
]>
Thoughts
Minus
operations to calculate the average cost of Minus
. It took 125 seconds to complete the compilation. So a single Minus
operation (~30 iterations) takes 9.65e-6 seconds to be performed.What now?
Minus
) every time, we could just compute its output once, and this is what would end up in a generated d.ts
. This could (maybe) be determined whether a type is waiting for arguments (can't be computed) or is argument-less (can be computed).@ahejlsberg @RyanCavanaugh @DanielRosenwasser
@jcalz Circular conditional type police? I would like to turn myself in,
I just wrote a generic compose()
function with no overloads,
//(a: number, b: string) => Date | null
const many = compose(
(a : number, b : string) => a+b,
(s : string) => s.length > 2,
(b : boolean) => Math.random() > 0.5 && b ? new Date() : null
);
interface CompileError<_ErrorMessageT> {
__compileError : never
}
type PopFront<ArrT extends readonly any[]> = (
((...arr : ArrT) => void) extends ((head : any, ...tail : infer TailT) => void) ?
TailT :
never
);
type PushFront<TailT extends readonly any[], HeadT> = (
((head : HeadT, ...tail : TailT) => void) extends ((...arr : infer ArrT) => void) ?
ArrT :
never
);
//////////////////////////////////
type AssertCanComposeImpl<
F extends (...args : any[]) => any,
ArrT extends readonly ((arg : any) => any)[],
IndexT extends any[]
> = (
{
0 : unknown,
1 : (
[ReturnType<F>] extends Parameters<ArrT[0]> ?
AssertCanComposeImpl<
ArrT[0],
PopFront<ArrT>,
PushFront<IndexT, any>
> :
CompileError<[
"Output",
[ReturnType<F>],
"of function",
IndexT["length"],
"cannot be input to function taking",
Parameters<ArrT[0]>
]>
)
}[
ArrT["length"] extends 0 ?
0 :
1
]
);
type AssertCanCompose<
F extends (...args : any[]) => any,
ArrT extends readonly ((arg : any) => any)[]
> = (
number extends ArrT["length"] ?
unknown :
AssertCanComposeImpl<
F,
ArrT,
[]
>
);
//////////////////////////////////
type ComposeImpl<
F extends (...args : any[]) => any,
ArrT extends readonly ((arg : any) => any)[],
ParametersT extends any[]
> = (
{
0 : (...args : ParametersT) => ReturnType<F>,
1 : (
[ReturnType<F>] extends Parameters<ArrT[0]> ?
ComposeImpl<
ArrT[0],
PopFront<ArrT>,
ParametersT
> :
never
)
}[
ArrT["length"] extends 0 ?
0 :
1
]
);
type Compose<
F extends (...args : any[]) => any,
ArrT extends readonly ((arg : any) => any)[]
> = (
number extends ArrT["length"] ?
(...args : Parameters<F>) => unknown :
ComposeImpl<
F,
ArrT,
Parameters<F>
>
);
declare function compose<
P extends any[],
R extends any,
ArrT extends readonly ((arg : any) => any)[]
> (
f : (
(...args : P) => (
& R
& AssertCanCompose<
() => R,
ArrT
>
)
),
...arr : ArrT
) : (
Compose<(...args : P) => R, ArrT>
);
//Error: Expected at least 1 arguments, but got 0.
compose();
//(a: number, b: string) => unknown
const doNotKnowReturnType = compose(
(a : number, b : string) => a+b,
...[]
);
//(a: number, b: string) => string
const one = compose(
(a : number, b : string) => a+b
);
//(a: number, b: string) => Date | null
const many = compose(
(a : number, b : string) => a+b,
(s : string) => s.length > 2,
(b : boolean) => Math.random() > 0.5 && b ? new Date() : null
);
//const cannotCompose: never
//
const cannotCompose = compose(
/*
CompileError<[
"Output",
[Date],
"of function", 1,
"cannot be input to function taking",
[boolean]
]>
*/
(a : number, b : string) => a+b,
(s : string) => new Date(), //Uh oh, next function expects boolean
(b : boolean) => Math.random() > 0.5 && b ? new Date() : null
);
[Edit]
Just realized I left in the unnecessary IndexT. It was there because the assertion and actual composition were one type before. I split it into two types and forgot to clean up
[Edit]
Removed IndexT
from ComposeImpl<>
@AnyhowStep I only use recursion in cases where the length of a tuple changes. For all the rest (in most cases) you should be able to use mapped types (it applies in this case). You can check out my implementation of compose
:
https://github.com/pirix-gh/ts-toolbelt/blob/master/src/Function/Compose.ts
Someone on the TS gitter was asking for a compose function signature with arbitrary amounts of functions as inputs. So, I thought it was an interesting request.
I didn't want to just post the answer on gitter and let the code disappear into the ether, though :x
I gave your impl a brief look.
Looks like mine composes forward and yours composes backwards?
Also, mine checks if the output of function N can be an input to function N+1 and gives an appropriate compile error (if a tuple is inferred)
I think your Composer
type can easily be made to check input and output types, though.
Also, if TS didn't have that max depth limit, my impl would have arbitrary length support.
But, realistically, since we do have the limit, it craps out after about 21 functions.
I think yours supports up to 40? 50? I forgot how many your Next
type supports
If we just use your Compose
type, though, it supports the max tuple length TS supports. But we lose the output-input type checks
It composes forward, as it's a mapped type, but it will block at 40 yhea. And it does check for input-output, what do you mean?
I was referring to this part,
CompileError<[
"Output",
[Date],
"of function", 1,
"cannot be input to function taking",
[boolean]
]>
If they call the compose()
function and the output of N doesn't feed into function N+1, it gives a compile-time error
A little out of topic but variadic type params seem less useful with tuples and recursive conditional types
Pinging everyone following this. A PR allowing recurive types has been accepted by the TypeScript team and is now part of their tests. So we can now write such recursive types, at the cost of using a library.
https://github.com/microsoft/TypeScript/pull/33810 shows how they work in their most basic form. I recommend you use this as a template. If the template does not fit, make sure your recursive types have proper terminating conditions.
For obvious reasons, the TypeScript team is not responsible for maintaining such techniques, this relies on me. But rest assured, this is well tested as it is part of ramda as well, it is used by many people.
I'm not sure this can close this issue, but is nevertheless a great step forward.
@pirix-gh Can you toss in type-level generator trampolines to those tests, too? =P
I'd rather love to see type providers one day, to have top performance types.
Is there any way to get a version of this that works in the Playground? Or some guidance on how to do this without using an external library? Like maybe we can make a self-contained "ts-toolbelt lite" that can be used inline as opposed to in a module hierarchy? I'm hoping to minimize Stack Overflow and GitHub issue headaches.
@sandersn if I made a PR to add trampolines to the tests, would it be accepted?
The following file gives an example of what the test would look like,
https://github.com/AnyhowStep/tsql/blob/master/src/tuple-util/reverse.ts
It doesn't rely on external dependencies to work and can easily go beyond 1000+ iterations/recursive calls
[Edit]
Trampolines saved my life with #33541
https://github.com/microsoft/TypeScript/issues/33541#issuecomment-565765544
Another example of trampolines for recursive types,
https://github.com/AnyhowStep/tsql/pull/65/files#diff-0214b2fefabaf1617e690660683db775R226-R237
@jcalz, I'm working on a script that will make it work on the playground (flattening), will ping when it's done
There is the possibility that the current implementation relies on variance markers for conditional types, which are known to have bugs. When I've looked at fixing these issues I've run into problems where conditional types that were checked trivially now fail because they are checked structurally.
For instance, when investigating #31277 I got an infinite type error in something originating in ts-toolbelt
. This was pre-3.7, and I haven't looked at how that affects things yet.
When I get a chance I'll see if the changes in #31277 caues any regressions in #33810.
Erm, I was the one who merged that test. My intent in adding it to the test suite was not, _not_ to support recursive types. It was to let us (and anybody who wants to follow our user tests) know when we break the latest workarounds. That is, we'll consider that test passing even if there are compilation errors or OOMs.
We have a recursive type limit for a reason — people don't expect a compiler to do arbitrary computation, so compile-time programs that you write need to run fast. This is especially true for commonly-used libraries.
@AnyhowStep That particular test is for ts-toolbelt. Is ts-toolbelt trampolined? If not, I'm fine with adding a separate test in index.ts. @pirix-gh is that OK with you?
I don't think ts-toolbelt is trampolined. I can say I have not noticed a negative impact on performance due to trampolines, though.
Trampolines can be an important workaround, even when recursive types are not deeply nested. In the above linked issue (#33541), I only had a recursive type doing two iterations and TS still gave up (unless assigning the result to a temporary variable).
With the trampoline workaround, it works fine with 60+ iterations, whether I assign to a temporary variable or not.
I can make a separate PR that tests trampolines. I didn't even realize it was a thing that could be OK'd
Nope, it's not trampolined, one of my goals is keep ts' memory as sane as I can. Recursive types do get limited by the compiler when they get too complex internally, within themselves (instantiation depth). What I do for recursive types, and that is what @sandersn means by workaround, is that I use:
type RecursiveType<A, B...> =
_RecursiveType<A, B...> extends infer X
? X
: never
This is in a sense a mini-trampoline, to allow combining recursive types together without hitting that instantiation depth error (buildup from the recursive type). I feel like this is already asking a lot from ts. But if you remove the extends infer X
part you are left with a fully supported type, as it uses no trick IMHO... However, if I were to remove this on the ts-toolbelt, types like Curry
would certainly not be possible.
So I use extends infer X
to ask ts to give me more, and is optional when you write your own types.
(Just to give an idea, ts-toolbelt standalone weighs ~40MB
, and with all its tests, it climbs up to ~90MB
).
I think trampolines belongs to a separate PR. I'd be interested to see memory & performance on them @AnyhowStep.
I've seen that infer X
trick fail in many (of my) use cases. Which is why I opt for a full trampoline nowadays.
import {Reverse} from "./reverse";
export type X = Reverse<[
//50
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//100
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//150
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//200
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//250
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//287
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7
]>;
> tsc
Files: 34
Lines: 7450
Nodes: 21256
Identifiers: 6781
Symbols: 214344
Types: 92308
Memory used: 85343K
Assignability cache size: 2014
Identity cache size: 0
Subtype cache size: 0
I/O Read time: 0.00s
Parse time: 0.12s
Program time: 0.13s
Bind time: 0.05s
Check time: 0.81s
transformTime time: 0.02s
Source Map time: 0.01s
commentTime time: 0.01s
I/O Write time: 0.00s
printTime time: 0.04s
Emit time: 0.04s
Total time: 1.02s
And another test with,
import {Concat} from "./concat";
export type Y = Concat<
[
//50
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//100
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//150
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//200
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//250
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//287
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7
],
[
//50
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//100
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//150
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//200
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//250
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
//287
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7
]
>;
declare function foo () : Y;
export const x = foo();
> tsc
Files: 35
Lines: 7542
Nodes: 21877
Identifiers: 6794
Symbols: 485364
Types: 220970
Memory used: 156981K
Assignability cache size: 3546
Identity cache size: 0
Subtype cache size: 0
I/O Read time: 0.01s
Parse time: 0.14s
Program time: 0.16s
Bind time: 0.06s
Check time: 1.91s
transformTime time: 0.03s
Source Map time: 0.01s
commentTime time: 0.00s
I/O Write time: 0.00s
printTime time: 0.06s
Emit time: 0.06s
Total time: 2.19s
And the output,
import { Concat } from "./concat";
export declare type Y = Concat<[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7]>;
export declare const x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7];
//# sourceMappingURL=index.d.ts.map
Not bad, considering I just concated two tuples, length 287 each, to become one tuple of length 574 during compile-time. (I don't actually do this in my application code, though).
The most I've done in my projects is concated a tuple of length 50+, I think.
https://github.com/AnyhowStep/ts-trampoline-test
I just realized looking at Memory used
isn't too helpful for such small examples. When I put the Reverse
and Concat
tests together, memory usage was 153478K
, for a single run.
Memory usage for Concat
alone was 156981K
, for a single run. (Notice it is higher than Concat + Reverse
tests combined)
Reverse
alone was 85343K
, for a single run.
@pirix-gh how are you benchmarking performance and memory usage?
And what's your "largest" test? I know you have a hard limit of 50 for each recursive type, but is there a particular test where you compose types until they just become a massive computational nightmare?
I am reminded:
It's too late to decide that we're not ready for recursive conditional types; they're here already. Now let's make them safe and easy to use! Maybe some kind of Defer<T, D, N>
where T
is some arbitrarily self-referencing type, D
is some default type in case T
can't be resolved by recursing at most N
levels deep, and you can leave off N
and get the maximum value allowed by the compiler, which is something big.
Otherwise I either have to keep giving answers like this one and/or impersonating an officer of the law.
Hmm, look at that! Recursive flat()
! (#32131) Not sure what to make of it.
EDIT: #38298 β
Oh wow #40002 π π π π
Most helpful comment
I am reminded:
It's too late to decide that we're not ready for recursive conditional types; they're here already. Now let's make them safe and easy to use! Maybe some kind of
Defer<T, D, N>
whereT
is some arbitrarily self-referencing type,D
is some default type in caseT
can't be resolved by recursing at mostN
levels deep, and you can leave offN
and get the maximum value allowed by the compiler, which is something big.Otherwise I either have to keep giving answers like this one and/or impersonating an officer of the law.