Repository navigation
Bind anonymous recursive types during declaration serialization - #64752
Jake Bailey (jakebailey) wants to merge 4 commits into
Conversation
Anonymous cycles behind optional members, unions, and generic scopes need reference points before serialization changes. Declaration-only checking must also retain anonymous object returns and imported recursive conditional signatures without silently acquiring any. Record the current diagnostics and emitted declarations so the subsequent fix can be reviewed independently of the added coverage. For 52415 For 64656 For 58696
Value projections do not necessarily denote the recursive constituent being serialized, especially through optional or union-valued members. Anonymous cycles therefore need bindings independent of their placement inside an exported declaration. Captured generic scopes and declaration visibility must survive that representation so consumers retain the original recursive relationships. Declaration emit can introduce private names, while displays have no place to define them. This difference must be explicit policy rather than an accidental consequence of tracker capabilities. Fixes 52415 Fixes 64656 For 44045
There was a problem hiding this comment.
🟡 Changes recommended
Synthesized aliases can collide in global declaration scope across independently emitted script files.
1 open finding
What changed in this PR
Enables declaration emit to synthesize type aliases for anonymous recursive types instead of reporting TS5088.
Changes:
- Adds recursive type-binding and alias-synthesis infrastructure.
- Hoists generated aliases into source-file or namespace scopes.
- Expands compiler, incremental, and quick-info coverage and updates affected baselines.
| File | Description |
|---|---|
tsc/testdata/tests/cases/compiler/recursiveFunctionDeclarationSerialization.ts |
Adds recursive serialization cases. |
tsc/testdata/baselines/reference/tsc/incremental/recursive-tagged-tuple-after-incremental-edits.js |
Updates incremental signatures. |
tsc/testdata/baselines/reference/tsc/generateTrace/generateTrace-with-multiple-files-and-complex-types.js |
Updates trace type records. |
tsc/testdata/baselines/reference/fourslash/quickInfo/recursiveFunctionCallQuickInfo.baseline |
Updates recursive quick info. |
tsc/testdata/baselines/reference/conformance/varianceAnnotations.js |
Adds synthesized declaration output. |
tsc/testdata/baselines/reference/conformance/varianceAnnotations.errors.txt |
Removes resolved TS5088 diagnostic. |
tsc/testdata/baselines/reference/conformance/protectedClassPropertyAccessibleWithinNestedClass(target=es2015).types |
Updates protected-member recursive types. |
tsc/testdata/baselines/reference/conformance/privateClassPropertyAccessibleWithinNestedClass(target=es2015).types |
Updates private-member recursive types. |
tsc/testdata/baselines/reference/conformance/commonJSImportNestedClassTypeReference.js |
Emits recursive class helper alias. |
tsc/testdata/baselines/reference/conformance/commonJSImportNestedClassTypeReference.errors.txt |
Removes resolved serialization error. |
tsc/testdata/baselines/reference/compiler/reverseMappedDeclarationSerialization.errors.txt |
Removes recursive mapped-type error. |
tsc/testdata/baselines/reference/compiler/recursiveFunctionDeclarationSerialization.js |
Records expanded declaration output. |
tsc/testdata/baselines/reference/compiler/recursiveFunctionDeclarationSerialization.errors.txt |
Updates expected diagnostics. |
tsc/testdata/baselines/reference/compiler/recursiveContainerDeclarationSerialization.js |
Emits recursive container aliases. |
tsc/testdata/baselines/reference/compiler/recursiveContainerDeclarationSerialization.errors.txt |
Removes resolved container errors. |
tsc/testdata/baselines/reference/compiler/noImplicitThisBigThis.js |
Emits recursive object aliases. |
tsc/testdata/baselines/reference/compiler/noImplicitThisBigThis.errors.txt |
Removes resolved object errors. |
tsc/testdata/baselines/reference/compiler/jsFunctionWithPrototypeNoErrorTruncationNoCrash.types |
Preserves recursive prototype types. |
tsc/testdata/baselines/reference/compiler/isolatedDeclarationErrorsReturnTypes.types |
Updates recursive arrow inference. |
tsc/testdata/baselines/reference/compiler/emitClassExpressionInDeclarationFile.js |
Emits recursive class-expression aliases. |
tsc/testdata/baselines/reference/compiler/emitClassExpressionInDeclarationFile.errors.txt |
Removes resolved class-expression error. |
tsc/testdata/baselines/reference/compiler/declarationEmitUnserializableTypes.js |
Emits formerly unserializable local types. |
tsc/testdata/baselines/reference/compiler/declarationEmitUnserializableTypes.errors.txt |
Removes resolved local-type errors. |
tsc/testdata/baselines/reference/compiler/declarationEmitStaticMethodNonIdentifierNames.js |
Emits recursive static-method alias. |
tsc/testdata/baselines/reference/compiler/declarationEmitStaticMethodNonIdentifierNames.errors.txt |
Removes resolved static-method error. |
tsc/testdata/baselines/reference/compiler/declarationEmitInferredTypeAlias4.js |
Emits inferred recursive alias. |
tsc/testdata/baselines/reference/compiler/declarationEmitInferredTypeAlias4.errors.txt |
Removes resolved inference error. |
tsc/testdata/baselines/reference/compiler/arrayFakeFlatNoCrashInferenceDeclarations.js |
Emits recursive conditional helper. |
tsc/testdata/baselines/reference/compiler/arrayFakeFlatNoCrashInferenceDeclarations.errors.txt |
Removes resolved flat-array error. |
tsc/internal/transformers/declarations/transform.go |
Enables and inserts synthesized aliases. |
tsc/internal/transformers/declarations/tracker.go |
Tracks alias destination scopes. |
tsc/internal/nodebuilder/types.go |
Extends node-builder flags and tracker API. |
tsc/internal/fourslash/tests/recursiveFunctionQuickInfo_test.go |
Adds recursive hover coverage. |
tsc/internal/execute/tsctests/tsc_test.go |
Expands incremental consumer coverage. |
tsc/internal/checker/symboltracker.go |
Forwards alias tracking and captures parameters. |
tsc/internal/checker/pseudotypenodebuilder.go |
Uses binding-aware back-references. |
tsc/internal/checker/nodecopy.go |
Forwards alias tracker operations. |
tsc/internal/checker/nodebuilderscopes.go |
Separates bindings from structural expansion. |
tsc/internal/checker/nodebuilderimpl.go |
Integrates alias synthesis and caching. |
tsc/internal/checker/nodebuilderbindings.go |
Implements synthesized recursive aliases. |
tsc/internal/checker/nodebuilder.go |
Initializes and emits alias state. |
tsc/internal/checker/checker_test.go |
Tests synthesis flag behavior. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| name := b.e.Factory.NewUniqueNameEx("_recursive", printer.AutoGenerateOptions{ | ||
| Flags: printer.GeneratedIdentifierFlagsOptimistic, | ||
| }) |
There was a problem hiding this comment.
Yeah. I don't know what we could possibly to do fix this.
There was a problem hiding this comment.
that already had to be an issue with synthesized mixin bases, right? so at the very least - that's not a new issue
There was a problem hiding this comment.
Overloads ruin the idea it seems:
// a.ts
function make(value: number) {
return { value, self() { return this; } };
}
// b.ts
function make(value: string) {
return { value, self() { return this; } };
}Would emit
// a.d.ts
type make_recursive = { value: number; self(): make_recursive };
declare function make(value: number): make_recursive;
// b.d.ts
type make_recursive = { value: string; self(): make_recursive };
declare function make(value: string): make_recursive;Then those conflict globally
There was a problem hiding this comment.
Pushed anyway
There was a problem hiding this comment.
couldnt this emit be conditional and only used when serializing modules?
Independently emitted scripts can introduce identical recursive helper names into the shared global type namespace. Consuming both declarations can then confuse their return types, including with skipLibCheck.
Independently emitted scripts share a global type namespace, so generic recursive helper names can cause duplicate declarations and confuse otherwise unrelated return types, even with skipLibCheck. Overloads sharing the same global owner still need disambiguation.

WIP