microsoft / microsoft/TypeScript

tsserver hangs indefinitely when inferring return type of class method that passes this to a function with deeply nested conditional return type

Open
#63,207 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug Domain: check: Type Circularity
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

### 🔎 Search Terms

hang circular

### 🕗 Version & Regression Information

This behavior is present in TypeScript 5 and TypeScript 6 beta.

### ⏯ Playground Link

N/A — requires the remeda package (npm i remeda). Reproduction is a self-contained file below.

### 💻 Code

```ts
import { pick } from "remeda";

export class MyClass {
constructor(public readonly field: number) {}

// [bad] tsserver hangs indefinitely: no explicit return type
getIdentity() {
return pick(this, ["field"]);
}

// [good] works fine: explicit return type breaks the cycle
getIdentityFixed(): Pick {
return pick(this, ["field"]);
}
}
```

### 🙁 Actual behavior

`tsserver` becomes completely unresponsive. The editor (VS Code) loses all language service features (hover types, completions, diagnostics) until tsserver is manually restarted. No error is emitted; tsserver simply loops forever.

The cause is an undetected circular type inference chain. To infer the return type of `getIdentity()`, TypeScript must evaluate `PickFromArray` (remeda's `pick` return type). That involves `IsBoundedRecord` → `IsBounded>`. `KeysOfUnion` uses `UnionToIntersection`, which places `MyClass` in a contravariant function parameter position — forcing eager, non-deferred evaluation of the full structural type of `MyClass`. The full type of `MyClass` includes `getIdentity: () => `, whose inferred return type is what we started trying to compute. Because the cycle passes through ~5 distinct type alias instantiations, TypeScript's cycle-detection heuristics never fire, and the checker loops indefinitely instead of emitting a "circularly references itself" error.

### 🙂 Expected behavior

TypeScript should detect the circular inference and either:

- Emit a "Return type annotation circularly references itself" error (as it does for simpler cycles), or
- Fall back to any with a warning.

Either outcome is acceptable. The checker should never hang.

### Additional information about the issue

Workaround: add an explicit return type annotation to the method. This gives TypeScript the type of `MyClass.getIdentity()` upfront, preventing the cycle from forming.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by installing remeda and running the self-contained TypeScript reproduction with tsserver. Compare the hanging inferred-return case with the explicit Pick annotation, then trace circularity handling around the reported PickFromArray, IsBoundedRecord, IsBounded, KeysOfUnion, and UnionToIntersection chain. Done means tsserver no longer hangs and either reports a circular return-type error or falls back to any with a warning.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.