diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index efe984f219a4f..a4c1366c52387 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -694,6 +694,7 @@ type Checker struct { memberIndexSlotsArena core.Arena[uint32] freeTypeSets *temporaryTypeSet freePropertyCounts *temporaryPropertyCounts + freeNameSets []map[string]struct{} mergedSymbols map[*ast.Symbol]*ast.Symbol mergedExportsChecked collections.Set[*ast.Symbol] factory ast.NodeFactory @@ -19328,12 +19329,13 @@ func (c *Checker) getPropertiesOfObjectType(t *Type) []*ast.Symbol { func (c *Checker) getPropertiesOfUnionOrIntersectionType(t *Type) []*ast.Symbol { d := t.AsUnionOrIntersectionType() if d.resolvedProperties == nil { - var checked collections.Set[string] + checked := c.getTemporaryNameSet() + defer c.releaseTemporaryNameSet(checked) props := []*ast.Symbol{} for _, current := range d.types { for _, prop := range c.getPropertiesOfType(current) { - if !checked.Has(prop.Name()) { - checked.Add(prop.Name()) + if _, ok := checked[prop.Name()]; !ok { + checked[prop.Name()] = struct{}{} combinedProp := c.getPropertyOfUnionOrIntersectionType(t, prop.Name(), t.flags&TypeFlagsIntersection != 0 /*skipObjectFunctionPropertyAugment*/) if combinedProp != nil { props = append(props, combinedProp) diff --git a/tsc/internal/checker/utilities.go b/tsc/internal/checker/utilities.go index 517ffcba86cc2..2c0c4055ce114 100644 --- a/tsc/internal/checker/utilities.go +++ b/tsc/internal/checker/utilities.go @@ -957,6 +957,26 @@ func (c *Checker) releaseTemporaryPropertyCounts(counts *temporaryPropertyCounts c.freePropertyCounts = counts } +// Property enumeration of a combined type may recursively enumerate other +// combined types, so each call borrows a separate name set. +func (c *Checker) getTemporaryNameSet() map[string]struct{} { + if n := len(c.freeNameSets); n != 0 { + set := c.freeNameSets[n-1] + c.freeNameSets = c.freeNameSets[:n-1] + return set + } + return make(map[string]struct{}) +} + +func (c *Checker) releaseTemporaryNameSet(set map[string]struct{}) { + // Do not retain unusually large tables in the free list. + if len(set) > 4096 { + return + } + clear(set) + c.freeNameSets = append(c.freeNameSets, set) +} + func (s *orderedSet[T]) contains(value T) bool { if s.valuesByKey == nil { return slices.Contains(s.values, value)