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)
- 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
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