Count intersection property names with a plain map and slice (#64876)

This commit is contained in:
Jake Bailey authored and GitHub committed 2026-10-27 23:49:28 +00:00
1 parent b6a965883b
commit 615d415931
3 files changed
+76 -23

No files matched your search

+11 -5
View File
@@ -22457,16 +22457,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.getLayoutPropertiesOfType(types[0]) {
counts.AddNew(prop.Name())
}
for _, t := range types[1:] {
for _, prop := range c.getLayoutPropertiesOfType(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
}
}
+22 -15
View File
@@ -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)
}
}
+43 -3
View File
@@ -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 {