Unbounded memory in declaration emit for an inferred type built from non-exported recursive aliases (also hit by `--noEmit --watch`)
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
- 難度
- 5/5
- 預估耗時
- 一週以上
- 新手友好度
- 12/100
- Issue 類型
- 缺陷
- 描述清晰度
- 基本清楚
- 活躍度
- 活躍
- 技術堆疊
- go, typescript
研究方向
Start with the two-module repro in types.ts and api.ts, then trace the sampled stack: checker NodeBuilderImpl.typeToTypeNode, createMappedTypeNodeFromType and conditionalTypeToTypeNode, which call ast NodeFactory.DeepCloneNode on a recursive alias body. Done means the declaration emit for api.ts terminates with either a truncated type or TS7056, and the noEmit --watch case stays bounded in memory, with the exported-alias variant still producing the same output.
由索引模型根據 Issue 內容生成。
描述
🔎 Search Terms
DeepCloneNode, conditionalTypeToTypeNode, declaration emit out of memory, recursive conditional type, non-exported type alias, TS7056, watch mode memory, noEmit watch
🕗 Version & Regression Information
- Reproduces on
7.0.2and on7.1.0-dev.20261009.1(macOS, arm64). - Not yet compared with 6.0.
⏯ Playground Link
Needs two modules (the helper aliases must live in another file and stay unexported), so the repro is below.
💻 Code
// types.ts
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : [];
type Shift<K, P extends unknown[]> = P[0] extends K ? Tail<P> : never;
type NonUndefined<T> = T extends undefined ? never : T;
type Rec<T, P extends string[]> = T extends object ? { [K in Extract<keyof T, P[0]>]: NonUndefined<Rec<T[K], Shift<K, P>>> } : T;
export type Expanded<T, P extends string> = Rec<T, [P]>;
export interface Entity { id: string }
// api.ts
import type { Entity, Expanded } from "./types";
declare function get<T>(): T;
export const fetch = <P extends string>() => get<Expanded<Entity, P>>();
// tsconfig.json
{ "compilerOptions": { "strict": true, "declaration": true, "emitDeclarationOnly": true, "outDir": "out", "types": [] }, "files": ["types.ts", "api.ts"] }
Run tsc -p .
🙁 Actual behavior
The compiler never finishes. Memory passes 3 GB within about 5 seconds and keeps growing until the process is killed. On a real project with the same pattern in 11 modules, a full declaration emit reached 52 GB in 3.5 minutes before we stopped it.
The same thing happens in a project that never asks for declarations, through watch mode. With "noEmit": true instead of the declaration options, and a third file importing fetch from ./api:
tsc -p . --watchcompletes the initial build normally (about 90 MB,Found 0 errors).- Append a comment to
api.ts. - The watcher starts declaration emit for the changed file and grows without bound (3 GB after 5 to 7 seconds in this repro). In our project the watcher reached a 65 GB footprint.
A plain tsc -p . --noEmit is fine (about 100 MB).
Sampled stack (symbolized from the release binary, condensed):
compiler.(*Program).Emit.func2
compiler.(*emitter).emitDeclarationFile
transformers/declarations.(*DeclarationTransformer).transformVariableDeclaration
transformers/declarations.(*DeclarationTransformer).ensureType
checker.(*NodeBuilderImpl).serializeTypeForDeclaration
checker.(*NodeBuilderImpl).serializeReturnTypeForSignature
checker.(*NodeBuilderImpl).typeToTypeNode
checker.(*NodeBuilderImpl).createMappedTypeNodeFromType
checker.(*NodeBuilderImpl).conditionalTypeToTypeNode (repeats)
ast.(*NodeFactory).DeepCloneNode
ast.(*ConditionalTypeNode).VisitEachChild (very deep)
ast.(*IndexedAccessTypeNode).VisitEachChild
Most of the remaining CPU time goes to GC (scanObject, findObject) and map lookups under printer.(*EmitContext).SetOriginalEx.
🙂 Expected behavior
Emit terminates, either with a truncated type or with TS7056. The nightly already does that for close variants of this repro:
| Variant | 7.0.2 | 7.1.0-dev.20261009.1 |
|---|---|---|
| Repro as above | runaway | runaway |
Without the NonUndefined<...> wrapper |
finishes, writes a 2.5 MB api.d.ts |
TS7056, about 30 MB |
Rec<T[K], P> instead of Rec<T[K], Shift<K, P>> |
finishes, writes a 660 KB api.d.ts |
TS7056, about 30 MB |
| Helper aliases exported | about 30 MB, 220 byte api.d.ts |
about 30 MB, 220 byte api.d.ts |
Additional information about the issue
- Workaround: export the helper aliases. The printer can then reference
import("./types").Rec<...>instead of inlining the alias body into itself. - The real world shape is a common "make these dotted paths required" helper (
DeepRequiredObjwith a path to tuple split), used as the type argument of a generic API client method without an explicit return type. - Possibly related: #63825 (closed without a repro, same
DeepCloneNoderunaway on the declaration emit path), #63966 / #63969, #64556. The nightly includes those fixes and still reproduces this one. - The watch mode part seems worth noting on its own: a
noEmitproject can run into declaration emit problems just by saving a file, because the builder computes.d.tssignatures for changed files.
- 主要語言
- Go
- 星號
- 112k
- 分支
- 15.4k
- 平均合併
- 3 天 1 小時
- 30 天內合併 PR
- 136
環境準備
在瀏覽器裡用你自己的 GitHub 帳號啟動這個專案的開發容器。
- 沒有 Dockerfile 或 Docker Compose 檔案
- 有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
microsoft/TypeScript 的其他 Issue
-
難度 2/5 1-3 小時 新手友好度 62/100
microsoft/TypeScript#65290 ·
維護者通常 1 天內回覆
-
Native API: formatDiagnosticsWithColorAndContext drops related information without a file location可能重新可做 關聯的 PR 已關閉且未合併。 未關閉
難度 2/5 1-3 小時 新手友好度 75/100
microsoft/TypeScript#65087 ·
維護者通常 1 天內回覆
-
TS7 LSP: documentHighlight returns nothing when the file path contains '@'可能重新可做 關聯的 PR 已關閉且未合併。 未關閉Bug
難度 2/5 1-3 小時 新手友好度 78/100
microsoft/TypeScript#64758 ·
維護者通常 1 天內回覆
-
Language server (tsc --lsp) exits with code 1 after `shutdown` and `exit`可能已有人在做 @rajnish-verma-22172 於 13 天前認領。 未關閉Bug
難度 2/5 1-3 小時 新手友好度 76/100
microsoft/TypeScript#64742 · 1 則留言 ·
維護者通常 1 天內回覆
-
Non-enum CLI options with multiple values separated by comma and space aren't whitespace trimmed可能已有人在做 @RyanCavanaugh 於 28 天前認領。 未關閉Bug
難度 2/5 1-3 小時 新手友好度 68/100
microsoft/TypeScript#64618 · 已指派 1 人 ·
維護者通常 1 天內回覆
查看 microsoft/TypeScript 的全部 Issue
相似的 Issue
-
CLI: `describe <ns>.<table>` reports "namespace does not exist" when only the table is missing可能已有人在做 @Shruthirdy 今天認領。 未關閉
難度 2/5 1-3 小時 新手友好度 62/100
apache/iceberg-go#2423 ·
維護者通常 2 天內回覆
-
post-v4
難度 2/5 1-3 小時 新手友好度 62/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 80/100
rishikanthc/Scriberr#506 ·
-
[Bug] rollingUpdatePartition panics when live StatefulSet replicas sit far below the desired count未關閉
難度 2/5 1-3 小時 新手友好度 78/100
sgl-project/rbg#547 ·
維護者通常 1 天內回覆
-
area: frontend priority: normal
難度 2/5 1-3 小時 新手友好度 78/100
維護者通常 1 天內回覆