Skip to content

Bind anonymous recursive types during declaration serialization - #64752

Draft
Jake Bailey (jakebailey) wants to merge 4 commits into
jakebailey/fix-recursive-function-declarationsfrom
jakebailey/recursive-type-aliases
Draft

Jake Bailey (jakebailey) wants to merge 4 commits into
jakebailey/fix-recursive-function-declarationsfrom
jakebailey/recursive-type-aliases

Conversation

@jakebailey

Copy link
Copy Markdown
Member

WIP

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment on lines +50 to +52
name := b.e.Factory.NewUniqueNameEx("_recursive", printer.AutoGenerateOptions{
Flags: printer.GeneratedIdentifierFlagsOptimistic,
})

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I don't know what we could possibly to do fix this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that already had to be an issue with synthesized mixin bases, right? so at the very least - that's not a new issue

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed anyway

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@jakebailey
Jake Bailey (jakebailey) marked this pull request as draft October 13, 2026 22:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

3 participants