-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Tail recursion optimization limit 999 may be manipulated #49459
Comments
Is this really a bug?
type NumberToTuple3<N, T extends 0[] = []> =
T['length'] extends N ? T : (NumberToTuple3<N, [...T, 0]> & {}); which doesn't: type BigT_Computed = NumberToTuple1<500> // ok
type BigT_StillOkay = NumberToTuple2<500> // ok
type BigT_Bad = NumberToTuple3<500> // ☠ I admit I'd think (Also, you confused me for quite a bit by shadowing the |
It's a bug in the sense that if someone can and will fix it, I'm not going to stop them. That's about it though. |
@jcalz thank you for the comment, I've renamed @RyanCavanaugh thank you for taking a look! Not sure that there is something can be fixed but ensuring that the limit for both types would be the same (probably 999?) A funny thing to note is that adding more levels of nasting would get the limit back to 999 type NumberToTuple3<Num extends number, Tuple extends 0[] = []> = 0 extends 1 ? never : 0 extends 1 ? never :
Tuple['length'] extends Num ? Tuple : NumberToTuple2<Num, [...Tuple, 0]>;
type StillWorking = NumberToTuple3<999>
type AlreadyAnError = NumberToTuple3<1000> |
Any conclusions? |
I faced a similar issue and I've investigated the compiler for a moment. Here I left what I've found for people who might wonder what's going on here. I was looking for tricks to bypass recursion depth limit, eventually I've arrived to this magical type: type Magic<T> = T Wrapping any tail recursive conditional type with this type allows it bypass iteration limit of 1000. type NumberToTuple3<Num extends number, Tuple extends 0[] = []> =
Magic<Tuple['length'] extends Num ? Tuple : NumberToTuple3<Num, [...Tuple, 0]>>;
// It's okay to duplicate `Magic`
type NumberToTuple4<Num extends number, Tuple extends 0[] = []> =
Magic<Magic<Tuple['length'] extends Num ? Tuple : NumberToTuple3<Num, [...Tuple, 0]>>>; Surprised, I looked over the compiler, and found a function called if (newRoot.aliasSymbol) {
tailCount++;
} This conditional statement was left with following comment:
It seems the author expected every recursive conditional type to have type Magic<T> = T
type NTuple<N extends number, T, R extends unknown[] = []> = Magic<R['length'] extends N ? R : NTuple<N, T, [T, ...R]>>
type Inc<N extends number> = [0, ...NTuple<N, 0>]['length']
type Test = Inc<1003> I don't know how P.S this bug seems to exist from the beginning of TCO for recursive conditional types. (#45711) |
Bug Report
🔎 Search Terms
tail recursion, tail recursion optimization, tail recursion detection, tail recursion limit
🕗 Version & Regression Information
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
NumberToTuple1
is using the tail recursion optimization soBigTuple_Computed
type is[0, 0, 0, ..., 0]
, just as expected.NumberToTuple2
isNOT using tail recursion optimizationusing tail recursion optimization in a different maneer soBigTuple_Error
type isany
.The difference between them is only in junk piece of code
0 extends 1 ? never :
that is doing nothing.UPD:
NumberToTuple2
is working up to 999, whileNumberToTuple1
is working up to 3153🙂 Expected behavior
According to the article introducing tail call optimization to TypeScript, tail call optimization should be applied to bothRecursion limit is expected to remain the sameNumberToTuple1
andNumberToTuple2
the same, because non of them is doing any on-stack transformations with the call results.The text was updated successfully, but these errors were encountered: