Unbounded memory in declaration emit for an inferred type built from non-exported recursive aliases (also hit by `--noEmit --watch`)
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 12/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- go, typescript
- Área
- build-system, compilers
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
🔎 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.
- Lenguaje dominante
- Go
- Estrellas
- 112k
- Forks
- 15.4k
- Merge medio
- 2 d 21 h
- PR fusionados (30 d)
- 126
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de microsoft/TypeScript
-
Won't Fix
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
microsoft/TypeScript#65107 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Native API: formatDiagnosticsWithColorAndContext drops related information without a file locationPosiblemente ocupada @timurzvural la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
microsoft/TypeScript#65087 ·
Los mantenedores suelen responder en 1 día
-
TS7 LSP: documentHighlight returns nothing when the file path contains '@'Quizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. AbiertoBug
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
microsoft/TypeScript#64758 ·
Los mantenedores suelen responder en 1 día
-
Language server (tsc --lsp) exits with code 1 after `shutdown` and `exit`Posiblemente ocupada @rajnish-verma-22172 la tomó hace 13 días. AbiertoBug
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
microsoft/TypeScript#64742 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Class names `eval` and `arguments` are not reported as invalid strict mode bindingsPosiblemente ocupada @dmety la tomó hace 26 días. AbiertoBug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
microsoft/TypeScript#64661 ·
Los mantenedores suelen responder en 1 día
Todos los issues de microsoft/TypeScript
Issues similares
-
needs-area
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Azure/typespec-azure#5936 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 70/100
-
Fix mode unpins a reusable workflow call when the same repository is also used as a step actionAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
github/gh-actions-lock#261 ·
Los mantenedores suelen responder en 1 día
-
bug documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
labmonkeys-space/nl6#974 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100