Deduplicate repeated declarations on union/intersection properties#63119
Open
Andarist wants to merge 1 commit intomicrosoft:mainfrom
Open
Deduplicate repeated declarations on union/intersection properties#63119Andarist wants to merge 1 commit intomicrosoft:mainfrom
Andarist wants to merge 1 commit intomicrosoft:mainfrom
Conversation
Collaborator
|
This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes an OOM issue caused by a double cross-product of union/intersection types.
Problem
Certain patterns involving union types with cross-product intersections cause
createUnionOrIntersectionPropertyto accumulate an enormous number of duplicate declaration references on synthetic union/intersection property symbols. With just 3 shapes, a single propertywaccumulates 1,611 declarations from only 4 unique declaration nodes. Growth is N⁴ in the number of shapes — the original tldraw repro (16 shapes) OOMs: tldraw/tldraw#7755Root cause
Three mechanisms combine to produce multiplicative growth:
1. Cross-product explosion in
getIntersectionTypeShapePartialWithDimensions<T>referencesTLShapePartial<ShapeWithDimensions<T>>twice — once directly, once via an indexed access["props"]:These resolve independently over the shape union. When their
propstypes are intersected,getIntersectionTypedistributes over both unions, producing N×N members (3 shapes → 9 members).2. Nested cross-product in
getNarrowableTypeForReferenceEach of the 9 members still contains an unresolved indexed access
TLShapePartial<ShapeWithDimensions<N>>["props"]referencing the generic type parameter.getNarrowableTypeForReferenceresolves these by substituting N's constraint, which involves looking uppropsonShapeWithDimensions<N>:ShapeWithDimensions<T>has the same structure — two independent references to T throughprops. When the constraint (3-shape union) is substituted,T.props→ UNION(3) andT["props"]→ UNION(3) get intersected → another 3×3 cross-product. So each of the 9 members expands to 9 (not 3), giving 9×9 = 81 total members (N² × N² = N⁴).3.
addRange(declarations, prop.declarations)without deduplicationIn
createUnionOrIntersectionProperty, declarations from each constituent property are concatenated viaaddRangewithout deduplication:When
propis a synthetic property from a priorcreateUnionOrIntersectionPropertycall, its.declarationsis already an accumulated array from that earlier resolution. Through nested union/intersection resolution, the same few declaration nodes get concatenated over and over, with each level multiplying the count while the unique set stays constant.Fix
Deduplicate declarations in
createUnionOrIntersectionProperty. This caps the declarations array at the actual unique count at every nesting level, preventing the entire cascade.Walkthrough (3 shapes)
Consider 3
TLShapesubtypes with distinct props, each containing propertyw, plus aDimensionstype — 4 unique declaration nodes total:The problematic type accesses
props?.won a generic constrained to a cross-product-producing intersection:The two references to
TLShapePartial<ShapeWithDimensions<T>>inShapePartialWithDimensionsdistribute independently → 3×3 = 9 member cross-product (mechanism 1). ThengetNarrowableTypeForReferenceresolves each member's unresolved indexed access throughShapeWithDimensions<N>, which has its own two independent references to N — producing another 3×3 cross-product per member → 9×9 = 81 total (mechanism 2). The declarations accumulate through mechanism 3:wdeclswdeclarations on each typePartial<Tᵢ_props & Dimensions> & Partial<Tⱼ_props & Dimensions> & Dimensions)DimensionsEach union level multiplies by its fan-out. Each intersection level sums its members' already-accumulated arrays. Unique count never changes — with deduplication, the total stays at 4 regardless of the number of shapes.