diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 10ddb3c0c88b4..4e03977978f36 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -22321,16 +22321,22 @@ func (c *Checker) somePropertyReducesToNever(t *Type) bool { // Collect declaration counts for each property across all constituent types of the intersection. counts := c.getTemporaryPropertyCounts() defer c.releaseTemporaryPropertyCounts(counts) - for _, t := range t.Types() { + // Property names are unique within each constituent, so the first + // constituent's names can be added without looking them up. + types := t.Types() + for _, prop := range c.getPropertiesOfType(types[0]) { + counts.AddNew(prop.Name()) + } + for _, t := range types[1:] { for _, prop := range c.getPropertiesOfType(t) { - counts.Set(prop.Name(), counts.GetOrZero(prop.Name())+1) + counts.Add(prop.Name()) } } // Check if any property appears in more than one constituent type and reduces to 'never'. // Go in the order the properties were found so the combined properties are created in the same order every time. - for propName, count := range counts.Entries() { - if count > 1 { - if prop := c.getPropertyOfUnionOrIntersectionType(t, propName, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) { + for i := range counts.entries { + if e := counts.entries[i]; e.count > 1 { + if prop := c.getPropertyOfUnionOrIntersectionType(t, e.name, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) { return true } } diff --git a/tsc/internal/checker/types_test.go b/tsc/internal/checker/types_test.go index 86de2b089e06e..d3d7eff33a86a 100644 --- a/tsc/internal/checker/types_test.go +++ b/tsc/internal/checker/types_test.go @@ -307,33 +307,40 @@ func TestIntersectionReductionAllocations(t *testing.T) { //nolint:paralleltest func TestTemporaryPropertyCounts(t *testing.T) { t.Parallel() c := &Checker{} + names := func(p *temporaryPropertyCounts) []string { + var result []string + for _, e := range p.entries { + result = append(result, e.name) + } + return result + } outer := c.getTemporaryPropertyCounts() - outer.Set("second", 1) - outer.Set("first", 1) - outer.Set("second", 2) - assert.Assert(t, slices.Equal(slices.Collect(outer.Keys()), []string{"second", "first"})) + outer.Add("second") + outer.Add("first") + outer.Add("second") + assert.Assert(t, slices.Equal(names(outer), []string{"second", "first"})) inner := c.getTemporaryPropertyCounts() assert.Assert(t, inner != outer) - inner.Set("first", 4) - assert.Equal(t, outer.GetOrZero("first"), 1) - assert.Equal(t, outer.GetOrZero("second"), 2) + inner.Add("first") + assert.Equal(t, outer.Count("first"), 1) + assert.Equal(t, outer.Count("second"), 2) c.releaseTemporaryPropertyCounts(inner) assert.Equal(t, inner.Size(), 0) - assert.Assert(t, !inner.Has("first")) + assert.Equal(t, inner.Count("first"), 0) reused := c.getTemporaryPropertyCounts() assert.Equal(t, reused, inner) - reused.Set("second", 3) - reused.Set("first", 5) - assert.Assert(t, slices.Equal(slices.Collect(reused.Keys()), []string{"second", "first"})) - assert.Equal(t, outer.GetOrZero("first"), 1) + reused.Add("second") + reused.Add("first") + assert.Assert(t, slices.Equal(names(reused), []string{"second", "first"})) + assert.Equal(t, outer.Count("first"), 1) c.releaseTemporaryPropertyCounts(reused) c.releaseTemporaryPropertyCounts(outer) runtime.GC() reused = c.getTemporaryPropertyCounts() assert.Equal(t, reused.Size(), 0) - assert.Assert(t, !reused.Has("second")) + assert.Equal(t, reused.Count("second"), 0) for i := range 4097 { - reused.Set(strconv.Itoa(i), i) + reused.Add(strconv.Itoa(i)) } c.releaseTemporaryPropertyCounts(reused) for counts := c.freePropertyCounts; counts != nil; counts = counts.next { @@ -364,7 +371,7 @@ func TestIntersectionReductionReentrancy(t *testing.T) { assert.Equal(t, c.freePropertyCounts, workspace) for counts := c.freePropertyCounts; counts != nil; counts = counts.next { assert.Equal(t, counts.Size(), 0) - assert.Assert(t, !counts.Has("value")) + assert.Equal(t, counts.Count("value"), 0) } } diff --git a/tsc/internal/checker/utilities.go b/tsc/internal/checker/utilities.go index 3a7e9993ba879..16a5fff074ba5 100644 --- a/tsc/internal/checker/utilities.go +++ b/tsc/internal/checker/utilities.go @@ -8,7 +8,6 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/binder" - "github.com/microsoft/TypeScript/tsc/internal/collections" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/debug" "github.com/microsoft/TypeScript/tsc/internal/diagnostics" @@ -933,9 +932,50 @@ func (c *Checker) releaseTemporaryTypeSet(set *temporaryTypeSet) { c.freeTypeSets = set } +// temporaryPropertyCounts counts property names in first-occurrence order. type temporaryPropertyCounts struct { - collections.OrderedMap[string, int] - next *temporaryPropertyCounts + entries []propertyCount + indices map[string]int32 + next *temporaryPropertyCounts +} + +type propertyCount struct { + name string + count int32 +} + +func (p *temporaryPropertyCounts) Add(name string) { + if i, ok := p.indices[name]; ok { + p.entries[i].count++ + return + } + p.AddNew(name) +} + +// AddNew adds a name known not to have been added yet, skipping the lookup. +func (p *temporaryPropertyCounts) AddNew(name string) { + if p.indices == nil { + p.indices = make(map[string]int32) + } + p.indices[name] = int32(len(p.entries)) + p.entries = append(p.entries, propertyCount{name: name, count: 1}) +} + +func (p *temporaryPropertyCounts) Count(name string) int { + if i, ok := p.indices[name]; ok { + return int(p.entries[i].count) + } + return 0 +} + +func (p *temporaryPropertyCounts) Size() int { + return len(p.entries) +} + +func (p *temporaryPropertyCounts) Clear() { + clear(p.entries) + p.entries = p.entries[:0] + clear(p.indices) } func (c *Checker) getTemporaryPropertyCounts() *temporaryPropertyCounts {