Fixes for recursive declarations, elided placeholders, cycles - #64432
Jake Bailey (jakebailey) wants to merge 11 commits into
Conversation
Recursive types that cannot be serialized and inferred types that exceed the serialization depth limit can produce declarations containing any instead of reporting an error. Record the existing declaration output and include explicit type annotations as controls.
When inferred types cannot be serialized, declaration emit must report cyclic structure or truncation errors rather than substitute any. Keep type display unchanged.
Record the serialization depth limit errors for recursive arrays and tuples, with finite types and accessible type aliases as controls. Include initial and unchanged incremental builds for the recursive tuple test. Add the comment-only edit with the fix because that edit hangs before the fix. For 63825
Deferred and regular type references can represent the same array or tuple. Declaration emit expands these types structurally, so both references must use the same entry in visitedTypes to detect a cycle. Otherwise incremental declaration emit can repeatedly expand the type. Use the regular type reference for the target and resolved type arguments for cycle detection. Include the previously hanging incremental edits with the fix. Fixes 63825
Comment-only edits can resolve element types of recursive mapped arrays and tuples during incremental declaration emit, introducing diagnostics after a successful initial build. Cover these incremental differences, circular type arguments, finite instantiations, and declaration emit. Add the compiler test that fails the pre-emit/post-emit diagnostic count check with the fix so each commit remains runnable. For 64405
Recursive mapped arrays and tuples need deferred type references so recursive instantiations can use the reference before its type arguments are resolved. Resolving the element types eagerly instead reaches the type instantiation depth limit and makes incremental builds disagree with non-incremental builds. Use the mapped type declaration and instantiated outer type parameters for recursion identity. Preserve type aliases without modifying shared instantiations. Keep tuples with variadic elements eager because mapped element types can change the tuple structure. Include the compiler test that fails the pre-emit/post-emit diagnostic count check without the fix. Fixes 64405 For 58696
Recursive function types with accessible names should use typeof in emitted declarations. Cover arrows, function expressions, function declarations, and consumption of the emitted declarations across incremental builds. Include variables with type annotations that differ from their function initializers so typeof does not substitute the variable's different type.
Recursive function types can use typeof with their variable's symbol when the variable has the same type. Not using this accessible symbol causes unnecessary cyclic structure errors. Using a variable with a different annotated type instead loses the function type. Track visited types when serializing structural pseudotypes so checker fallbacks detect recursive references without adding another call signature. Fixes 55832
Placeholders for reverse mapped properties and index signatures cause declaration emit to replace property and index signature types with any. Consumers of the declarations then accept assignments rejected by the original types. Cover finite and recursive reverse mapped types, including recursive instantiations with changing type arguments. Check incremental builds and keep existing quick info output as a control.
Reverse mapped type placeholders are intended for type display, but they also discard property and index signature types during declaration emit. Serialize those types for declarations while keeping type display unchanged. Reverse mapped types without symbols can still be recursive. Include them in visitedTypes and limit expansion by mapped type declaration when changing type arguments produce new recursion identities. Report cyclic structure or truncation errors instead of substituting any.
|
TypeScript Bot (@typescript-bot) test it |
|
Starting jobs; this comment will be updated as builds start and complete.
|
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It changes core type instantiation, relation, and declaration-serialization behavior across many existing compiler scenarios.
Review effort: Balanced
Findings: None
What changed in this PR
Fixes recursive type serialization, deferred mapped array/tuple handling, and silent declaration-emit any fallbacks.
Changes:
- Reports unserializable cyclic declarations instead of emitting elided
any. - Preserves recursive function and reverse-mapped declaration types.
- Defers mapped container instantiation and adds extensive regression coverage.
| File | Description |
|---|---|
tsc/testdata/tests/cases/compiler/reverseMappedDeclarationSerialization.ts |
Tests reverse-mapped declaration emit. |
tsc/testdata/tests/cases/compiler/recursiveMappedArray.ts |
Tests recursive mapped arrays. |
tsc/testdata/tests/cases/compiler/recursiveFunctionDeclarationSerialization.ts |
Tests recursive function serialization. |
tsc/testdata/tests/cases/compiler/recursiveContainerDeclarationSerialization.ts |
Tests recursive container serialization. |
tsc/testdata/tests/cases/compiler/deferredMappedTypeRelations.ts |
Tests deferred mapped-type relations. |
tsc/testdata/tests/cases/compiler/deferredMappedTypeDeclarationDepth.ts |
Tests serialization depth limits. |
tsc/testdata/tests/cases/compiler/deferredMappedTypeCircularArguments.ts |
Tests circular mapped arguments. |
tsc/testdata/tests/cases/compiler/deferredMappedTypeAliasInstantiation.ts |
Tests deferred alias instantiation. |
tsc/testdata/tests/cases/compiler/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.ts |
Updates recursive serialization expectations. |
tsc/testdata/tests/cases/compiler/declarationEmitUnserializableTypes.ts |
Tests diagnostics for unserializable types. |
tsc/testdata/baselines/reference/fourslash/quickInfo/reverseMappedDeclarationQuickInfo.baseline |
Records reverse-mapped hover output. |
tsc/testdata/baselines/reference/conformance/varianceAnnotations.js |
Removes invalid declaration output. |
tsc/testdata/baselines/reference/conformance/varianceAnnotations.errors.txt |
Records cyclic serialization diagnostic. |
tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.types |
Updates deferred mapped-type displays. |
tsc/testdata/baselines/reference/conformance/recursiveMappedTypes.errors.txt |
Updates relation diagnostics. |
tsc/testdata/baselines/reference/conformance/partiallyNamedTuples.types |
Preserves mapped tuple aliases. |
tsc/testdata/baselines/reference/conformance/newTarget.es6.types |
Updates recursive function display. |
tsc/testdata/baselines/reference/conformance/newTarget.es5(target=es2015).types |
Updates recursive function display. |
tsc/testdata/baselines/reference/conformance/mappedTypeWithAny.types |
Preserves mapped array aliases. |
tsc/testdata/baselines/reference/conformance/mappedTypesGenericTuples.types |
Preserves deferred tuple aliases. |
tsc/testdata/baselines/reference/conformance/mappedTypesArraysTuples.types |
Records deferred mapped containers. |
tsc/testdata/baselines/reference/conformance/localesObjectArgument.types |
Preserves Readonly aliases. |
tsc/testdata/baselines/reference/conformance/functionImplementations.types |
Updates recursive function identity. |
tsc/testdata/baselines/reference/conformance/commonJSImportNestedClassTypeReference.js |
Removes invalid cyclic declaration. |
tsc/testdata/baselines/reference/conformance/commonJSImportNestedClassTypeReference.errors.txt |
Records cyclic declaration error. |
tsc/testdata/baselines/reference/compiler/thisInPropertyBoundDeclarations.types |
Updates recursive structural display. |
tsc/testdata/baselines/reference/compiler/stripMembersOptionality2(exactoptionalpropertytypes=true).types |
Preserves mapped tuple aliases. |
tsc/testdata/baselines/reference/compiler/stripMembersOptionality2(exactoptionalpropertytypes=false).types |
Preserves mapped tuple aliases. |
tsc/testdata/baselines/reference/compiler/reverseMappedDeclarationSerialization.types |
Records inferred reverse-mapped types. |
tsc/testdata/baselines/reference/compiler/reverseMappedDeclarationSerialization.symbols |
Records test symbols. |
tsc/testdata/baselines/reference/compiler/reverseMappedDeclarationSerialization.js |
Records correct declaration emit. |
tsc/testdata/baselines/reference/compiler/reverseMappedDeclarationSerialization.errors.txt |
Records recursive mapping errors. |
tsc/testdata/baselines/reference/compiler/recursiveMappedArray.types |
Records recursive mapped-array type. |
tsc/testdata/baselines/reference/compiler/recursiveMappedArray.symbols |
Records test symbols. |
tsc/testdata/baselines/reference/compiler/recursiveMappedArray.js |
Records declaration emit. |
tsc/testdata/baselines/reference/compiler/recursiveFunctionDeclarationSerialization.types |
Records recursive function types. |
tsc/testdata/baselines/reference/compiler/recursiveFunctionDeclarationSerialization.symbols |
Records recursive symbols. |
tsc/testdata/baselines/reference/compiler/recursiveFunctionDeclarationSerialization.js |
Records typeof declaration emit. |
tsc/testdata/baselines/reference/compiler/recursiveFunctionDeclarationSerialization.errors.txt |
Records expected misuse error. |
tsc/testdata/baselines/reference/compiler/recursiveContainerDeclarationSerialization.types |
Records recursive container types. |
tsc/testdata/baselines/reference/compiler/recursiveContainerDeclarationSerialization.symbols |
Records container symbols. |
tsc/testdata/baselines/reference/compiler/recursiveContainerDeclarationSerialization.js |
Records valid named declarations. |
tsc/testdata/baselines/reference/compiler/recursiveContainerDeclarationSerialization.errors.txt |
Records unserializable container errors. |
tsc/testdata/baselines/reference/compiler/readonlyTupleAndArrayElaboration.types |
Preserves Readonly representation. |
tsc/testdata/baselines/reference/compiler/readonlyTupleAndArrayElaboration.errors.txt |
Updates diagnostic type text. |
tsc/testdata/baselines/reference/compiler/optionalTupleElementsAndUndefined.types |
Preserves mapped tuple alias. |
tsc/testdata/baselines/reference/compiler/objectFromEntries.types |
Preserves Readonly aliases. |
tsc/testdata/baselines/reference/compiler/objectFreeze.types |
Preserves Readonly aliases. |
tsc/testdata/baselines/reference/compiler/objectFreeze.errors.txt |
Updates diagnostic type text. |
tsc/testdata/baselines/reference/compiler/noImplicitThisBigThis.types |
Truncates recursive display safely. |
tsc/testdata/baselines/reference/compiler/noImplicitThisBigThis.js |
Removes invalid declarations. |
tsc/testdata/baselines/reference/compiler/noImplicitThisBigThis.errors.txt |
Records cyclic serialization errors. |
tsc/testdata/baselines/reference/compiler/mappedArrayTupleIntersections.types |
Records deferred mapped intersections. |
tsc/testdata/baselines/reference/compiler/inferRestArgumentsMappedTuple.types |
Preserves mapped tuple aliases. |
tsc/testdata/baselines/reference/compiler/functionExpressionReturningItself.types |
Uses variable recursion identity. |
tsc/testdata/baselines/reference/compiler/functionExpressionReturningItself.js |
Emits recursive typeof reference. |
tsc/testdata/baselines/reference/compiler/emitClassExpressionInDeclarationFile.js |
Removes invalid cyclic declaration. |
tsc/testdata/baselines/reference/compiler/emitClassExpressionInDeclarationFile.errors.txt |
Records cyclic class error. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeRelations.types |
Records relation test types. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeRelations.js |
Records relation test emit. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeDeclarationDepth.js |
Records finite declaration output. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeDeclarationDepth.errors.txt |
Records growing-type depth errors. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeCircularArguments.types |
Records circular argument types. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeCircularArguments.symbols |
Records circular argument symbols. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeCircularArguments.js |
Records emitted fallback types. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeCircularArguments.errors.txt |
Records array and tuple cycle errors. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeAliasInstantiation.types |
Records deferred alias types. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeAliasInstantiation.symbols |
Records alias symbols. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeAliasInstantiation.js |
Records preserved alias declarations. |
tsc/testdata/baselines/reference/compiler/deferredMappedTypeAliasInstantiation.errors.txt |
Records element assignment errors. |
tsc/testdata/baselines/reference/compiler/declFileTypeofFunction.js |
Emits recursive function typeof. |
tsc/testdata/baselines/reference/compiler/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.symbols |
Updates test commentary. |
tsc/testdata/baselines/reference/compiler/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.js |
Removes truncated declarations. |
tsc/testdata/baselines/reference/compiler/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.errors.txt |
Records serialization-limit errors. |
tsc/testdata/baselines/reference/compiler/declarationEmitUnserializableTypes.types |
Records unserializable inferred types. |
tsc/testdata/baselines/reference/compiler/declarationEmitUnserializableTypes.symbols |
Records test symbols. |
tsc/testdata/baselines/reference/compiler/declarationEmitUnserializableTypes.js |
Emits only serializable declarations. |
tsc/testdata/baselines/reference/compiler/declarationEmitUnserializableTypes.errors.txt |
Records cycle and depth diagnostics. |
tsc/testdata/baselines/reference/compiler/declarationEmitStaticMethodNonIdentifierNames.js |
Removes invalid recursive declaration. |
tsc/testdata/baselines/reference/compiler/declarationEmitStaticMethodNonIdentifierNames.errors.txt |
Records recursive private-method error. |
tsc/testdata/baselines/reference/compiler/declarationEmitMappedTypePreservesTypeParameterConstraint.types |
Preserves mapped alias display. |
tsc/testdata/baselines/reference/compiler/declarationEmitInferredTypeAlias4.js |
Removes invalid cyclic declaration. |
tsc/testdata/baselines/reference/compiler/declarationEmitInferredTypeAlias4.errors.txt |
Records cyclic return-type error. |
tsc/testdata/baselines/reference/compiler/classExpressionInClassStaticDeclarations.js |
Removes invalid cyclic declaration. |
tsc/testdata/baselines/reference/compiler/classExpressionInClassStaticDeclarations.errors.txt |
Records cyclic static-property error. |
tsc/internal/fourslash/tests/reverseMappedDeclarationQuickInfo_test.go |
Tests reverse-mapped Quick Info. |
tsc/internal/fourslash/tests/recursiveFunctionQuickInfo_test.go |
Tests recursive function Quick Info. |
tsc/internal/checker/types.go |
Documents deferred mapped nodes. |
tsc/internal/checker/relater.go |
Adds mapped recursion identities. |
tsc/internal/checker/pseudotypenodebuilder.go |
Tracks structural pseudotype recursion. |
tsc/internal/checker/nodebuilderimpl.go |
Detects cycles and improves declaration serialization. |
tsc/internal/checker/checker.go |
Defers mapped arrays/tuples and preserves aliases. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
|
TypeScript Bot (@typescript-bot) run dt (this will be a lie but I'm just checking the bot) |
|
Jake Bailey (@jakebailey) Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Jake Bailey (@jakebailey) Here are the results of running the user tests with tsc comparing There were infrastructure failures potentially unrelated to your change:
Otherwise... Something interesting changed - please have a look. Details
|
|
Jake Bailey (@jakebailey) Here are the results of running the top 400 repos with tsc comparing Something interesting changed - please have a look. Details
|
Distinct deferred references to equivalent mapped arrays and tuples
prevent member instantiations from sharing cached results. This adds
substantial work in webpack without changing the resulting types.
Member instantiation needs a regular reference for sharing, while the
original deferred reference must retain its alias and recursion
identity.
Measured with 16 interleaved runs after warmup. pr.bench is the
published PR; candidate.bench includes this change. The sec/op metric
measures check time.
│ pr.bench │ candidate.bench │
│ sec/op │ sec/op vs base │
Webpack 1.873 ± 2% 1.787 ± 2% -4.59% (p=0.000 n=16)
Xstate 1.033 ± 2% 1.026 ± 2% ~ (p=0.570 n=16)
geomean 1.390 1.354 -2.66%
│ pr.bench │ candidate.bench │
│ allocs/op │ allocs/op vs base │
Webpack 6.603M ± 0% 6.224M ± 0% -5.74% (p=0.000 n=16)
Xstate 4.826M ± 0% 4.826M ± 0% ~ (p=0.752 n=16)
geomean 5.645M 5.481M -2.91%
e3e2b01 to
e52d5a1
Compare
|
TypeScript Bot (@typescript-bot) test it |
|
Jake Bailey (@jakebailey) Here they are:
tscComparison Report - baseline..pr
System info unknown
Hosts
Scenarios
Developer Information: |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hey Jake Bailey (@jakebailey), it looks like the DT test run failed. Please check the log for more details. |
|
Jake Bailey (@jakebailey) Here are the results of running the user tests with tsc comparing There were infrastructure failures potentially unrelated to your change:
Otherwise... Something interesting changed - please have a look. Details
|
|
Hey Jake Bailey (@jakebailey), it looks like the DT test run failed. Please check the log for more details. |
|
Jake Bailey (@jakebailey) Here are the results of running the top 400 repos with tsc comparing Something interesting changed - please have a look. Details
|
|
TypeScript Bot (@typescript-bot) test top1000 |
Muhammed Ali Kösen (mhalikosen)
left a comment
There was a problem hiding this comment.
I built this PR and main locally and ran the repros from #64405 and honojs/hono#5441. The #64405 cases are fixed: the a.ts + b.ts repro from the thread now keeps TS2322 at b.ts through the edit and the rerun, where main loses it.
Two cases still differ. Both pass a mapped type through infer A and check it against an array type, the pattern Hono's JSONParsed uses for arrays.
- Incremental. With
"incremental": true, build, add a comment to the file, build again. The second build reportserror TS4109: Type arguments for 'Array' circularly reference themselves.with no location, and a third build passes. main reports TS2589 at the same step; 6.0.3 passes.
type P<T> = { [K in keyof T]: P<T[K]> } extends infer A extends unknown[] ? A : { [K in keyof T]: P<T[K]> };
type Json = Json[];
declare function json<T>(object: T): P<T>;
const response = json({ value: null! as Json });- Declaration emit. With
--declaration, this PR reports TS5088 atresponse, where main and 7.0.2 emitvalue: Json(6.0.3 emitsvalue: any). The Hono route from honojs/hono#5441 fails the same way.
type P<T> = { [K in keyof T]: P<T[K]> } extends infer A extends unknown[] ? A : { [K in keyof T]: P<T[K]> };
type Json = string | Json[];
declare const value: Json;
declare function json<T>(object: T): P<T>;
export const response = json({ value });|
Jake Bailey (@jakebailey) Here are the results of running the top 1000 repos with tsc comparing Something interesting changed - please have a look. Details
|
|
Jake Bailey (@jakebailey) Here are some more interesting changes from running the top 1000 repos suite Details
|
|
Thanks for the info. I guess more stuff to figure out. |
|
After talking with Anders Hejlsberg (@ahejlsberg), I have #64452 instead. There are other things in this big PR that I will split out. |
Fixes #63825
Fixes #64405
Fixes #55832
This is a stack of a number of changes. It started off looking at #64405. On its face, that issue seemed like it was just going to be a Strada parity issue. Which it is, in a way, but it turns out that the errors that #64405 needed to "hide" were present in Strada, but were disabled and thrown away while not checking, meaning that it's basically more ghost errors.
The actual issue turned out to be a checker bug which led down a trail of other checker bugs, ones that were already open (and ones I wanted to fix at some point anyway).
So, this PR has 5 different fixes (in test+fix pairs for easier review):
createElidedInformationPlaceholderwhich then emits/* elided */ any, but that doesn't actually report any errors! We should really complain and not silently give anany. So, do that.DeepCloneNodeallocation runaway (44 GB / 308 s) for recursive tagged-tuple type alias #63825.It's possible some of this can be split apart into separate PRs, but all of this was found when I had copilot figure out the real cause of #64405 and I didn't think it was a good idea to lose the work, so I restructured it.