Skip to content

Commit e8b256d

Browse files
committed
Avoid map-backed sets for union and intersection property sources
Synthesizing or probing a union or intersection property gathers the distinct constituent properties into a set. That set rarely holds more than a handful of symbols, but collections.OrderedSet always allocates both a map and a key slice, and this runs for every property of every combined type that is enumerated during relation checks. Keep the distinct properties in a plain slice, and index them with a map only once the slice passes the same threshold used by orderedSet. mui-docs: │ before │ after │ │ allocs │ allocs vs base │ MUIDocs 25.19M ± 0% 23.32M ± 1% -7.41% (p=0.002 n=6) │ before │ after │ │ allocated-B │ allocated-B vs base │ MUIDocs 4.556Gi ± 0% 4.389Gi ± 1% -3.66% (p=0.002 n=6)
1 parent 2473949 commit e8b256d

1 file changed

Lines changed: 44 additions & 19 deletions

File tree

‎tsc/internal/checker/checker.go‎

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21953,8 +21953,12 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec
2195321953
// unionOrIntersectionPropertySources describes the constituent properties from
2195421954
// which a union or intersection property would be synthesized.
2195521955
type unionOrIntersectionPropertySources struct {
21956-
singleProp *ast.Symbol
21957-
propSet collections.OrderedSet[*ast.Symbol]
21956+
singleProp *ast.Symbol
21957+
// props holds distinct constituent properties in first-seen order, or is
21958+
// empty when every constituent shares singleProp. propsByKey indexes it
21959+
// only once it is large enough for hashing to beat a linear scan.
21960+
props []*ast.Symbol
21961+
propsByKey map[*ast.Symbol]struct{}
2195821962
indexTypes []*Type
2195921963
propFlags ast.SymbolFlags
2196021964
optionalFlag ast.SymbolFlags
@@ -21966,7 +21970,30 @@ type unionOrIntersectionPropertySources struct {
2196621970
// isSynthetic reports whether the sources require a new synthetic symbol rather
2196721971
// than reusing (or cloning) singleProp.
2196821972
func (s *unionOrIntersectionPropertySources) isSynthetic() bool {
21969-
return s.propSet.Size() != 0 || s.checkFlags&ast.CheckFlagsReadPartial != 0 || len(s.indexTypes) != 0
21973+
return len(s.props) != 0 || s.checkFlags&ast.CheckFlagsReadPartial != 0 || len(s.indexTypes) != 0
21974+
}
21975+
21976+
// addProp adds prop to props if it is not already present, preserving
21977+
// first-insertion order.
21978+
func (s *unionOrIntersectionPropertySources) addProp(prop *ast.Symbol) {
21979+
if s.propsByKey == nil {
21980+
if slices.Contains(s.props, prop) {
21981+
return
21982+
}
21983+
s.props = append(s.props, prop)
21984+
if len(s.props) > orderedSetMapThreshold {
21985+
s.propsByKey = make(map[*ast.Symbol]struct{}, len(s.props))
21986+
for _, p := range s.props {
21987+
s.propsByKey[p] = struct{}{}
21988+
}
21989+
}
21990+
return
21991+
}
21992+
if _, ok := s.propsByKey[prop]; ok {
21993+
return
21994+
}
21995+
s.props = append(s.props, prop)
21996+
s.propsByKey[prop] = struct{}{}
2197021997
}
2197121998

2197221999
func (c *Checker) getUnionOrIntersectionPropertySources(containingType *Type, name string, skipObjectFunctionPropertyAugment bool, sources *unionOrIntersectionPropertySources) bool {
@@ -21976,7 +22003,6 @@ func (c *Checker) getUnionOrIntersectionPropertySources(containingType *Type, na
2197622003
var checkFlags ast.CheckFlags
2197722004
var optionalFlag ast.SymbolFlags
2197822005
var singleProp *ast.Symbol
21979-
propSet := &sources.propSet
2198022006
var indexTypes []*Type
2198122007
if !isUnion {
2198222008
checkFlags = ast.CheckFlagsReadonly
@@ -22013,10 +22039,10 @@ func (c *Checker) getUnionOrIntersectionPropertySources(containingType *Type, na
2201322039
// back and not `Array<string>.length` when we're looking at a `.length` access on a `string[] | number[]`
2201422040
mergedInstantiations = singleProp.Parent() != nil && len(c.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.Parent())) != 0
2201522041
} else {
22016-
if propSet.Size() == 0 {
22017-
propSet.Add(singleProp)
22042+
if len(sources.props) == 0 {
22043+
sources.addProp(singleProp)
2201822044
}
22019-
propSet.Add(prop)
22045+
sources.addProp(prop)
2202022046
}
2202122047
// classes created by mixins are represented as intersections
2202222048
// and overriding a property in a derived class redefines it completely at runtime
@@ -22082,9 +22108,9 @@ func (c *Checker) getUnionOrIntersectionPropertySources(containingType *Type, na
2208222108
return false
2208322109
}
2208422110
if isUnion &&
22085-
(propSet.Size() != 0 || checkFlags&ast.CheckFlagsPartial != 0) &&
22111+
(len(sources.props) != 0 || checkFlags&ast.CheckFlagsPartial != 0) &&
2208622112
checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected|ast.CheckFlagsContainsWritePrivate|ast.CheckFlagsContainsWriteProtected) != 0 &&
22087-
!(propSet.Size() != 0 && c.hasCommonDeclaration(propSet)) {
22113+
!(len(sources.props) != 0 && c.hasCommonDeclaration(sources.props)) {
2208822114
// A property in a union has a private or protected declaration in one constituent, but is missing
2208922115
// or has a different declaration in another constituent. If the private or protected declaration is
2209022116
// for reading, we don't create a property.
@@ -22115,7 +22141,6 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
2211522141
return nil
2211622142
}
2211722143
singleProp := sources.singleProp
22118-
propSet := &sources.propSet
2211922144
indexTypes := sources.indexTypes
2212022145
propFlags := sources.propFlags
2212122146
optionalFlag := sources.optionalFlag
@@ -22146,8 +22171,8 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
2214622171
links.setWriteType(c, c.getWriteTypeOfSymbol(singleProp))
2214722172
return clone
2214822173
}
22149-
if propSet.Size() == 0 {
22150-
propSet.Add(singleProp)
22174+
if len(sources.props) == 0 {
22175+
sources.props = []*ast.Symbol{singleProp}
2215122176
}
2215222177
var declarations []*ast.Node
2215322178
var firstType *Type
@@ -22156,7 +22181,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s
2215622181
var writeTypes []*Type
2215722182
var firstValueDeclaration *ast.Node
2215822183
var hasNonUniformValueDeclaration bool
22159-
for prop := range propSet.Values() {
22184+
for _, prop := range sources.props {
2216022185
if firstValueDeclaration == nil {
2216122186
firstValueDeclaration = prop.ValueDeclaration()
2216222187
} else if prop.ValueDeclaration() != nil && prop.ValueDeclaration() != firstValueDeclaration {
@@ -22240,9 +22265,9 @@ func isPrototypeProperty(symbol *ast.Symbol) bool {
2224022265
return symbol.Flags()&ast.SymbolFlagsMethod != 0 || symbol.CheckFlags()&ast.CheckFlagsSyntheticMethod != 0
2224122266
}
2224222267

22243-
func (c *Checker) hasCommonDeclaration(symbols *collections.OrderedSet[*ast.Symbol]) bool {
22268+
func (c *Checker) hasCommonDeclaration(symbols []*ast.Symbol) bool {
2224422269
var commonDeclarations collections.Set[*ast.Node]
22245-
for symbol := range symbols.Values() {
22270+
for _, symbol := range symbols {
2224622271
if len(symbol.Declarations()) == 0 {
2224722272
return false
2224822273
}
@@ -22459,15 +22484,15 @@ func (c *Checker) intersectionPropertyReducesToNever(t *Type, name string) bool
2245922484
prop := c.getUnionOrIntersectionProperty(t, name, true /*skipObjectFunctionPropertyAugment*/)
2246022485
return prop != nil && c.isNeverReducedProperty(prop)
2246122486
}
22462-
if sources.propSet.Size() == 0 {
22463-
sources.propSet.Add(sources.singleProp)
22487+
if len(sources.props) == 0 {
22488+
sources.props = []*ast.Symbol{sources.singleProp}
2246422489
}
2246522490
checkFlags := sources.checkFlags
2246622491
var firstType *Type
2246722492
var firstValueDeclaration *ast.Node
2246822493
hasNonUniformValueDeclaration := false
22469-
propTypes := make([]*Type, 0, sources.propSet.Size()+len(sources.indexTypes))
22470-
for prop := range sources.propSet.Values() {
22494+
propTypes := make([]*Type, 0, len(sources.props)+len(sources.indexTypes))
22495+
for _, prop := range sources.props {
2247122496
if firstValueDeclaration == nil {
2247222497
firstValueDeclaration = prop.ValueDeclaration()
2247322498
} else if prop.ValueDeclaration() != nil && prop.ValueDeclaration() != firstValueDeclaration {

0 commit comments

Comments
 (0)