Skip to content

TSServer hangs when using recursive types on tuples and pressing "," #26155

Closed
@AlCalzone

Description

@AlCalzone

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
 */
export type Head<T extends any[]> =
	T extends [infer H, ...any[]] ? H : never;

/**
 * Returns all but the first item's type in a tuple/array
 */
export type Tail<T extends any[]> =
	((...args: T) => any) extends ((head: any, ...tail: infer R) => any) ? R : never;

/**
 * Returns the given tuple/array with the item type prepended to it
 */
export type Unshift<List extends any[], Item> =
	((first: Item, ...rest: List) => any) extends ((...list: infer R) => any) ? R : never;

/** Reverses the given list */
export type Reverse<List extends any[]> = _Reverse<List, []>;

export type _Reverse<Source extends any[], Result extends any[] = []> = {
	// If the source list is empty, return the result
	1: Result,
	// else prepend the head of source to the result list and
	// continue recursion with the tail of the source
	0: _Reverse<Tail<Source>, Unshift<Result, Head<Source>>>,
}[Source extends [] ? 1 : 0];

When opening a file where that type is used, VSCode's Intellisense shows the correct type after a minor delay:

type Foo = Reverse<[1, 2, 3]>; // [3, 2, 1]
const f: 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).

type Foo = 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:

type Foo = Reverse<[1, 2, 3,4]>; // ",4" pasted from somewhere => all good!

The same thing happens when changing a type to this recursive type

// start here
type Foo = [1, 2, 3];
// change to this:
type Foo = Reverse<[1, 2, 3]; // broken

but not doing it in this order:

// start here
type Foo = [1, 2, 3];
// change to this:
type Foo = <[1, 2, 3]>;
// then to this:
type Foo = Reverse<[1, 2, 3]>; // all good!

Related Issues: #24897

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFixedA PR has been merged for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions