microsoft / microsoft/TypeScript

Regression: recursive conditional+mapped "serialize" type no longer proves `Transform<X>` assignable to `X` on deeply-nested compound types (native-preview; works in tsc)

Open
#63,845 9 comments 0 reactions 0 assignees View on GitHub
Needs More Info
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

## Environment
- `@typescript/native-preview` (tsgo)
- **Last good:** `7.0.0-dev.20260421.2`
- **Broken:** `7.0.0-dev.20260618.1` … `7.0.0-dev.20260622.1` (latest) — all fail
- Stable `typescript@5.9.3`: **OK** (accepts the code)

So: same source, only the native-preview version changed → a tsgo regression somewhere between `20260421.2` and `20260618.1`.

## What breaks
A common recursive "serialize" utility type — maps `Date | bigint` → `string` recursively through objects and arrays, leaving everything else unchanged (so for a type `X` containing **no** `Date`/`bigint`, `Transform` is structurally identical to `X`):

```ts
type Transform = {
[K in keyof Base]: Exclude extends never ? Base[K]
: Exclude extends object ? (Exclude extends From ? To | Extract : Transform)
: Base[K];
};
type TransformJson = Transform;
```

For a **deeply-nested compound** type — recursion + arrays + an 8-member **discriminated union with a shared base** (`{base} & ({type:1;…} | {type:2;…} | …)`) nested several levels behind arrays — `tsgo` fails to prove `TransformJson` assignable to `X` even when `X` has no `Date`/`bigint` (i.e. when the transform is provably the identity):

```
error TS2322: Type 'Transform<…, bigint | Date, string>' is not assignable to type '…'.
Type 'Transform<{base} & (…union…), bigint | Date, string>' is not assignable to '{base} & (…union…)'.
```

`tsc` proves this assignable; `tsgo` (new builds) does not.

## Diagnosis / things tried (in case it helps narrow the regressed area)
- Adding an up-front "does this subtree contain `From`?" guard so the transform returns `Base` verbatim for no-`From` subtrees → flips the failure into **`TS2589` "Type instantiation is excessively deep and possibly infinite"** across the project, i.e. the extra recursive pass exhausts a depth/instantiation budget.
- Bounding the transform's recursion depth (no extra pass) → tsgo then *fully expands* the type (no TS2589) but **still** rejects the expanded `{base} & union` intersection-with-union assignment.
- So it looks like two compounding limits: (a) a recursion/instantiation budget that's tighter or counted differently than in tsc, and (b) assignability of a mapped-type result back to an intersection-with-union, at depth.

## Reproduction note (the awkward part)
I could **not** reduce this to a small standalone repro: the minimal/standalone versions of the exact pattern (even with the same 8-member intersection-with-union behind arrays) compile **clean** under both tsgo and tsc. It only reproduces inside a large real project (many hundreds of interacting `Transform<…>` instantiations across the program), which points at a **scale-dependent / cumulative** instantiation budget rather than a single-type defect.

Happy to help bisect against the dev-build range, or to share a private reproduction with the team if that's an option.

Contributor guide

Open the contributing guide

Research direction

Start by bisecting the native-preview builds between 7.0.0-dev.20260421.2 and 7.0.0-dev.20260618.1, comparing tsgo with tsc on the reported project pattern. Investigate the recursion or instantiation budget and deep mapped-type assignability; done means reproducing the regression, avoiding TS2589, and restoring the assignment without breaking existing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, typescript
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.