-
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
Feature request: lift the circular constraint for conditional types #26980
Comments
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
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 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.
|
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 type JSONResult = null | string | boolean | number | Record<string, JSONResult> | JSONResult[]; In this case, 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. |
Can we discuss whether we can have official approval for the types provided by the ts-toolbelt or not? Performance Since when? Is it safe? 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
What now?
|
@jcalz Circular conditional type police? I would like to turn myself in, I just wrote a generic //(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 |
@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 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 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 If we just use your |
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,
If they call the 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. #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, 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 Another example of trampolines for recursive types, |
@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 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 So I use (Just to give an idea, ts-toolbelt standalone weighs |
I think trampolines belongs to a separate PR. I'd be interested to see memory & performance on them @AnyhowStep. |
I've seen that 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
]>;
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();
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 usage for
@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 Otherwise I either have to keep giving answers like this one and/or impersonating an officer of the law. |
Oh wow #40002 🌟 🎉 🎆 👍 |
Search Terms
circular type conditional type
Suggestion
Currently,
Last1
is valid, but seemingly-equivalentLast2
is not:My suggestion is to make
Last2
valid, since the recursion isn't necessarily unbounded.Use Cases
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.)Examples
See above in the suggestion summary. Here's a concrete example of how this file (with dependency) would be simplified:
Currently, you could achieve similar via this, but as you can see, it's highly repetitive and boilerplatey:
(The original code snippet is linked to from here as a possible solution to the issue of
_.compose
,_.flow
, and friends.)Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: