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
35 changes: 23 additions & 12 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -21893,24 +21893,35 @@ func (c *Checker) getPropertyOfUnionOrIntersectionType(t *Type, name string, ski
// these partial properties when identifying discriminant properties, but otherwise they are filtered out
// and do not appear to be present in the union type.
func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol {
var cache ast.SymbolTable
data := t.AsUnionOrIntersectionType()
if skipObjectFunctionPropertyAugment {
cache = ast.GetSymbolTable(&t.AsUnionOrIntersectionType().propertyCacheWithoutFunctionPropertyAugment)
if prop := data.propertyCacheWithoutFunctionPropertyAugment[name]; prop != nil {
return prop
}
} else {
cache = ast.GetSymbolTable(&t.AsUnionOrIntersectionType().propertyCache)
}
if prop := cache[name]; prop != nil {
return prop
if data.propertyCache != nil {
if prop := data.propertyCache[name]; prop != nil {
return prop
}
}
// Complete non-augmented properties are also valid for augmented
// lookup. Share the entry instead of retaining it in both tables.
if prop := data.propertyCacheWithoutFunctionPropertyAugment[name]; prop != nil && prop.CheckFlags()&ast.CheckFlagsPartial == 0 {
return prop
}
}
prop := c.createUnionOrIntersectionProperty(t, name, skipObjectFunctionPropertyAugment)
if prop != nil {
cache[name] = prop
// Propagate an entry from the non-augmented cache to the augmented cache unless the property is partial.
if skipObjectFunctionPropertyAugment && prop.CheckFlags()&ast.CheckFlagsPartial == 0 {
augmentedCache := ast.GetSymbolTable(&t.AsUnionOrIntersectionType().propertyCache)
if augmentedCache[name] == nil {
augmentedCache[name] = prop
if skipObjectFunctionPropertyAugment {
ast.GetSymbolTable(&data.propertyCacheWithoutFunctionPropertyAugment)[name] = prop
if prop.CheckFlags()&ast.CheckFlagsPartial == 0 && data.propertyCache[name] == prop {
Comment thread
jakebailey marked this conversation as resolved.
delete(data.propertyCache, name)
if len(data.propertyCache) == 0 {
data.propertyCache = nil
}
Comment on lines +21918 to +21921

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not that familiar with this code path (I don't entirely know when you need one map but not the other), but I do wonder if you can hit a pathological case where you keep deleting/reallocating maps.

Also, I wonder if you can save anything by having one "steal" a map from the other if the size is just 1.

}
} else {
ast.GetSymbolTable(&data.propertyCache)[name] = prop
}
}
return prop
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,7 @@ type EvolvingArrayType struct {
type UnionOrIntersectionType struct {
StructuredType
types []*Type
propertyCache ast.SymbolTable
propertyCache ast.SymbolTable // Augmented entries not shared with the non-augmented cache.
propertyCacheWithoutFunctionPropertyAugment ast.SymbolTable
resolvedProperties []*ast.Symbol
}
Expand Down
78 changes: 78 additions & 0 deletions tsc/internal/checker/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,84 @@ func TestCompactValueSymbolLinksLayout(t *testing.T) {
assert.Equal(t, links.getContainingType(), types[2])
}

func TestUnionIntersectionPropertyCacheSharing(t *testing.T) {
t.Parallel()
for _, intersection := range []bool{false, true} {
for _, augmentedFirst := range []bool{false, true} {
t.Run(strconv.FormatBool(intersection)+"/"+strconv.FormatBool(augmentedFirst), func(t *testing.T) {
t.Parallel()
c, _ := newCompositeTestChecker()
members := make(ast.SymbolTable)
for i := range 300 {
name := strconv.Itoa(i)
members[name] = c.newSymbol(ast.SymbolFlagsProperty, name)
}
members[""] = c.newSymbol(ast.SymbolFlagsProperty, "")
object := c.newAnonymousType(nil, members, nil, nil, nil)
var typ *Type
if intersection {
typ = c.newIntersectionType(ObjectFlagsNone, []*Type{object, object})
} else {
typ = c.newUnionType(ObjectFlagsNone, []*Type{object, object})
}
data := typ.AsUnionOrIntersectionType()
for name, symbol := range members {
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, !augmentedFirst), symbol)
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, augmentedFirst), symbol)
}
assert.Equal(t, len(data.propertyCacheWithoutFunctionPropertyAugment), len(members))
assert.Assert(t, data.propertyCache == nil)
runtime.GC()
for name, symbol := range members {
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, true), symbol)
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, false), symbol)
}
runtime.KeepAlive(c)
})
}
}
}

func TestUnionIntersectionPropertyCacheModes(t *testing.T) {
t.Parallel()
for _, intersection := range []bool{false, true} {
t.Run(strconv.FormatBool(intersection), func(t *testing.T) {
t.Parallel()
c, _ := newCompositeTestChecker()
var typ *Type
if intersection {
typ = c.newIntersectionType(ObjectFlagsNone, nil)
} else {
typ = c.newUnionType(ObjectFlagsNone, nil)
}
data := typ.AsUnionOrIntersectionType()
assert.Assert(t, c.getUnionOrIntersectionProperty(typ, "missing", true) == nil)
assert.Assert(t, c.getUnionOrIntersectionProperty(typ, "missing", false) == nil)
assert.Assert(t, data.propertyCache == nil)
assert.Assert(t, data.propertyCacheWithoutFunctionPropertyAugment == nil)
data.propertyCacheWithoutFunctionPropertyAugment = make(ast.SymbolTable)
data.propertyCache = make(ast.SymbolTable)
for _, flags := range []ast.CheckFlags{ast.CheckFlagsReadPartial, ast.CheckFlagsWritePartial} {
name := strconv.Itoa(int(flags))
partial := c.newSymbolEx(ast.SymbolFlagsProperty, name, flags)
complete := c.newSymbol(ast.SymbolFlagsProperty, name)
data.propertyCacheWithoutFunctionPropertyAugment[name] = partial
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, true), partial)
assert.Assert(t, c.getUnionOrIntersectionProperty(typ, name, false) == nil)
assert.Equal(t, c.getPropertyOfUnionOrIntersectionType(typ, name, true) == nil, flags&ast.CheckFlagsReadPartial != 0)
data.propertyCache[name] = complete
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, name, false), complete)
}
nonAugmented := c.newSymbol(ast.SymbolFlagsProperty, "different")
augmented := c.newSymbol(ast.SymbolFlagsProperty, "different")
data.propertyCacheWithoutFunctionPropertyAugment["different"] = nonAugmented
data.propertyCache["different"] = augmented
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, "different", true), nonAugmented)
assert.Equal(t, c.getUnionOrIntersectionProperty(typ, "different", false), augmented)
})
}
}

func TestCompactStructuredTypeLayout(t *testing.T) {
t.Parallel()
assert.Equal(t, unsafe.Sizeof(StructuredType{}), unsafe.Sizeof(ConstrainedType{})+5*unsafe.Sizeof(uintptr(0)))
Expand Down
Loading