Unbounded memory in declaration emit for an inferred type built from non-exported recursive aliases (also hit by `--noEmit --watch`)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 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
- 平均マージ
- 2日 21時間
- マージ済み PR(30日)
- 126
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
microsoft/TypeScript のほかの issue
-
Won't Fix
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
microsoft/TypeScript#65107 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Native API: formatDiagnosticsWithColorAndContext drops related information without a file location対応中かも @timurzvural が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
microsoft/TypeScript#65087 ·
メンテナーはふだん 1 日以内に返信
-
TS7 LSP: documentHighlight returns nothing when the file path contains '@'再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン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 日以内に返信
-
Class names `eval` and `arguments` are not reported as invalid strict mode bindings対応中かも @dmety が 26 日前に担当しました。 オープンBug
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
microsoft/TypeScript#64661 ·
メンテナーはふだん 1 日以内に返信
microsoft/TypeScript の issue をすべて見る
似ている issue
-
needs-area
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
Azure/typespec-azure#5936 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 70/100
-
Fix mode unpins a reusable workflow call when the same repository is also used as a step actionオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
github/gh-actions-lock#261 ·
メンテナーはふだん 1 日以内に返信
-
bug documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
labmonkeys-space/nl6#974 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100