Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22447,16 +22447,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
}
}
Expand Down
37 changes: 22 additions & 15 deletions tsc/internal/checker/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand Down
46 changes: 43 additions & 3 deletions tsc/internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Comment on lines +937 to +938
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 {
Expand Down
Loading