You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TSServer hangs when using recursive types on tuples and pressing "," #26155
Disclaimer: This is probably not intended use of the type system but it might show an underlying issue so I still report it.
TypeScript Version: 3.1.0-dev.20180802
Search Terms: recursive conditional type tuple freeze
Code
While experimenting with the examples found in #24897 (comment), I noticed some weird behavior when it comes to recursive types. Apparently tail recursion on type members seems to work, which means it can be used to create crazy types like Reverse:
/** * Returns the first item's type in a tuple */exporttypeHead<Textendsany[]>=Textends[infer H, ...any[]] ? H : never;/** * Returns all but the first item's type in a tuple/array */exporttypeTail<Textendsany[]>=((...args: T)=>any)extends((head: any, ...tail: infer R)=>any) ? R : never;/** * Returns the given tuple/array with the item type prepended to it */exporttypeUnshift<Listextendsany[],Item>=((first: Item, ...rest: List)=>any)extends((...list: infer R)=>any) ? R : never;/** Reverses the given list */exporttypeReverse<Listextendsany[]>=_Reverse<List,[]>;exporttype_Reverse<Sourceextendsany[],Resultextendsany[]=[]>={// If the source list is empty, return the result1: Result,// else prepend the head of source to the result list and// continue recursion with the tail of the source0: _Reverse<Tail<Source>,Unshift<Result,Head<Source>>>,}[Sourceextends[] ? 1 : 0];
When opening a file where that type is used, VSCode's Intellisense shows the correct type after a minor delay:
typeFoo=Reverse<[1,2,3]>;// [3, 2, 1]constf: Foo=[2,1];// error: 2 is not assignable to 3
After adding a comma after the 3 in the recursive type, TSServers comes to a halt (1 core with full CPU usage).
typeFoo=Reverse<[1,2,3,]>;// FREEZE
However, if I paste a comma and a number from somewhere else in a single operation, it works just fine:
typeFoo=Reverse<[1,2,3,4]>;// ",4" pasted from somewhere => all good!
The same thing happens when changing a type to this recursive type
// start heretypeFoo=[1,2,3];// change to this:typeFoo=Reverse<[1,2,3];// broken
but not doing it in this order:
// start heretypeFoo=[1,2,3];// change to this:typeFoo=<[1,2,3]>;// then to this:typeFoo=Reverse<[1,2,3]>;// all good!
Potentially relevant: While the process hangs, its RAM usage continues to rise - as if its building some infinite signature help or something like that.
I might have a clue whats going on behind the scenes. Using typescript@3.1.0-dev.20180824 and the following code:
/** * Returns the length of an array or tuple */exporttypeLengthOf<Textendsany[]>=T["length"];/** * Returns the logarithm to the base 2 of the (L + 1) */exporttypeMagnitude<L>=numberextendsL ? never : (Lextends0|1|2|3|4|5|6|7 ?
Lextends0|1|2|3 ?
Lextends0|1 ?
Lextends0 ?
0 : 1 : 2 : 3 : 4);// Drop the first N items of TtypeDrop1<Textendsany[]>=((...args: T)=>void)extends((a1: any, ...rest: infer R)=>void) ? R : never;typeDrop2<Textendsany[]>=((...args: T)=>void)extends((a1: any,a2: any, ...rest: infer R)=>void) ? R : never;typeDrop4<Textendsany[]>=((...args: T)=>void)extends((a1: any,a2: any,a3: any,a4: any, ...rest: infer R)=>void) ? R : never;typeDrop8<Textendsany[]>=((...args: T)=>void)extends((a1: any,a2: any,a3: any,a4: any,a5: any,a6: any,a7: any,a8: any, ...rest: infer R)=>void) ? R : never;// Take the first N items of T1, reverse them and prepend them to T2typeConcatReverse1<T1extendsany[],T2extendsany[]>=((a1: T1[0], ...rest: T2)=>void)extends((...args: infer R)=>void) ? R : never;typeConcatReverse2<T1extendsany[],T2extendsany[]>=((a2: T1[1],a1: T1[0], ...rest: T2)=>void)extends((...args: infer R)=>void) ? R : never;typeConcatReverse4<T1extendsany[],T2extendsany[]>=((a4: T1[3],a3: T1[2],a2: T1[1],a1: T1[0], ...rest: T2)=>void)extends((...args: infer R)=>void) ? R : never;typeConcatReverse8<T1extendsany[],T2extendsany[]>=((a8: T1[7],a7: T1[6],a6: T1[5],a5: T1[4],a4: T1[3],a3: T1[2],a2: T1[1],a1: T1[0], ...rest: T2)=>void)extends((...args: infer R)=>void) ? R : never;exporttypeConcatReverse<T1extendsany[],T2extendsany[]=[]>={0: T2,// depending on the length of T1, take the first half of T1,// and prepend it to T2 in a reversed order1: ConcatReverse<Drop1<T1>,ConcatReverse1<T1,T2>>,2: ConcatReverse<Drop2<T1>,ConcatReverse2<T1,T2>>,3: ConcatReverse<Drop4<T1>,ConcatReverse4<T1,T2>>,4: ConcatReverse<Drop8<T1>,ConcatReverse8<T1,T2>>,}[Magnitude<T1["length"]>];/** * Reverses the given list * WARNING: Use at your own risk, this might crash TypeScript */exporttypeReverse<Textendsany[]>=ConcatReverse<T>;typeDropLast<Textendsany[],Uextendsany[]=Reverse<T>,// error here, but it works!Vextendsany[]=Drop1<U>,Wextendsany[]=Reverse<V>>=W;typeFoo=DropLast<[1,2,3,4,5]>;
Intellisense shows an error on the two lines using Reverse<>:
Apparently, a huge type is generated behind the scenes even without any parameters being provided
Update: Using typescript@3.1.0-dev.20180824, the original code no longer completely freezes the language server. It's still incredibly slow, but works.
That seems to be because ConcatReverse is now never (using today's typescript@next).
When replacing the now no longer functional version of that type with a naive implementation that just loops the tuple, there is still a large type generated without any passed type arguments. Although it is not as huge as it used to be:
exporttypeUnshift<Listextendsany[],Item>=((first: Item, ...rest: List)=>any)extends((...list: infer R)=>any) ? R : never;typeReverseNaive<Sourceextendsany[],Targetextendsany[]=[],Lextendsnumber=LengthOf<Source>,Leftextendsany[]=Drop1<Source>,Rightextendsany[]=Unshift<Target,Source[0]>,>={0: Target,1: ReverseNaive<Left,Right>,}[Source["length"]extends0 ? 0 : 1];typeDropLast<Textendsany[],Uextendsany[]=ReverseNaive<T>,// error here, but it works!Vextendsany[]=Drop1<U>,Wextendsany[]=ReverseNaive<V>>=W;
Activity
AlCalzone commentedon Aug 3, 2018
Potentially relevant: While the process hangs, its RAM usage continues to rise - as if its building some infinite signature help or something like that.
KiaraGrouwstra commentedon Aug 9, 2018
afaik TS did do stuff on trailing comma for Intellisense purposes... not sure what's happening here though.
AlCalzone commentedon Aug 24, 2018
I might have a clue whats going on behind the scenes. Using typescript@3.1.0-dev.20180824 and the following code:
Intellisense shows an error on the two lines using

Reverse<>:Apparently, a huge type is generated behind the scenes even without any parameters being provided
AlCalzone commentedon Aug 24, 2018
Update: Using typescript@3.1.0-dev.20180824, the original code no longer completely freezes the language server. It's still incredibly slow, but works.
Add test for signaure help with recursive type
AlCalzone commentedon Aug 31, 2018
Sheetal Nandi (@sheetalkamat) Ryan Cavanaugh (@RyanCavanaugh)
Do you consider this as fixed? If so, what about the HUGE type generated in my last example? That does not seem intended. Should I open another issue for that?
sheetalkamat commentedon Aug 31, 2018
with typescript@next there is no huge type generated that I can repro with your example.
AlCalzone commentedon Sep 1, 2018
That seems to be because

ConcatReverseis nownever(using today's typescript@next).When replacing the now no longer functional version of that type with a naive implementation that just loops the tuple, there is still a large type generated without any passed type arguments. Although it is not as huge as it used to be:
