diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 148cf5fde47dd..19c9876826949 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -7,6 +7,7 @@ import ( "iter" "maps" "math" + "math/bits" "slices" "strconv" "strings" @@ -692,6 +693,8 @@ type Checker struct { outerTypeParameterListArena core.Arena[[]*Type] valueSymbolExtraArena core.Arena[valueSymbolExtra] structuredTypeExtraArena core.Arena[structuredTypeExtra] + memberIndexArena core.Arena[memberIndex] + memberIndexSlotsArena core.Arena[uint32] lazyStateArena core.Arena[LazyState] memberLayoutArena core.Arena[MemberLayout] layoutMemberInfos map[string]MemberInfo @@ -4592,8 +4595,8 @@ func (c *Checker) getTypeWithoutSignatures(t *Type) *Type { if len(resolved.getSignatures()) != 0 { result := c.newObjectType(ObjectFlagsAnonymous, t.symbol) result.objectFlags |= ObjectFlagsMembersResolved - result.AsObjectType().members = resolved.getMembers() result.AsObjectType().properties = resolved.getProperties() + result.AsObjectType().members = resolved.members return result } case t.flags&TypeFlagsIntersection != 0: @@ -25851,11 +25854,58 @@ func (c *Checker) cloneTypeReference(source *Type) *Type { func (c *Checker) setStructuredTypeMembers(t *Type, members ast.SymbolTable, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { t.objectFlags |= ObjectFlagsMembersResolved data := t.AsStructuredType() - data.members = members + // Filtering named members may query this type's members before they're set. + data.members = nil + if members != nil { + data.members = c.memberIndexArena.New() + data.members.table = members + } data.properties = c.getNamedMembers(members, t.symbol) + // Module exports can change after they're resolved, so keep their table. + if data.members != nil && (t.symbol == nil || t.symbol.Flags()&ast.SymbolFlagsValueModule == 0) && symbolTableMatchesProperties(members, data.properties) { + data.members = c.newPropertyIndex(data.properties, data.members) + } c.setStructuredTypeSignatures(data, callSignatures, constructSignatures, indexInfos) } +// Reports whether every member is one of the properties, stored under its own +// name. Checking the table's pairs, rather than looking up each property by +// name, also rejects a symbol stored under a second key. +func symbolTableMatchesProperties(members ast.SymbolTable, properties []*ast.Symbol) bool { + if len(members) != len(properties) { + return false + } + for name, symbol := range members { + if symbol.Name() != name { + return false + } + } + return true +} + +// Indexes properties by name in place of their symbol table. Small property +// lists are searched directly and need no index. +func (c *Checker) newPropertyIndex(properties []*ast.Symbol, index *memberIndex) *memberIndex { + if len(properties) <= 8 { + return nil + } + if uint64(len(properties)) >= uint64(^uint32(0)) { + panic("Member index exhausted") + } + size := 1 << bits.Len(uint(len(properties))*2-1) + index.table = nil + index.slots = c.memberIndexSlotsArena.NewSlice(size) + mask := uint64(size - 1) + for i, symbol := range properties { + slot := xxh3.HashString(symbol.Name()) & mask + for index.slots[slot] != 0 { + slot = (slot + 1) & mask + } + index.slots[slot] = uint32(i + 1) + } + return index +} + func (c *Checker) setStructuredTypeSignatures(data *StructuredType, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { if data.extra == nil && callSignatures == nil && constructSignatures == nil && indexInfos == nil { return diff --git a/tsc/internal/checker/types.go b/tsc/internal/checker/types.go index f119288a21744..a4669dc1a4cb6 100644 --- a/tsc/internal/checker/types.go +++ b/tsc/internal/checker/types.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/collections" "github.com/microsoft/TypeScript/tsc/internal/core" "github.com/microsoft/TypeScript/tsc/internal/evaluator" + "github.com/zeebo/xxh3" ) //go:generate npx hereby generate:checker @@ -1114,7 +1115,7 @@ func (t *ConstrainedType) AsConstrainedType() *ConstrainedType { return t } type StructuredType struct { ConstrainedType - members ast.SymbolTable + members *memberIndex properties []*ast.Symbol extra *structuredTypeExtra } @@ -1145,10 +1146,15 @@ func (t *StructuredType) ConstructSignatures() []*Signature { func (t *StructuredType) getMember(name string) *ast.Symbol { if t.members != nil { - return t.members[name] + return t.members.get(name, t.properties) } lazy := t.getLazyState() if lazy == nil { + for _, symbol := range t.properties { + if symbol.Name() == name { + return symbol + } + } return nil } memberInfo := lazy.layout.memberInfoMap[name] @@ -1159,15 +1165,49 @@ func (t *StructuredType) getMember(name string) *ast.Symbol { } func (t *StructuredType) getMembers() ast.SymbolTable { - if t.members == nil { - if properties := t.getProperties(); len(properties) != 0 { - t.members = make(ast.SymbolTable, len(properties)) - for _, member := range properties { - t.members[member.Name()] = member - } + if t.members != nil && t.members.table != nil { + return t.members.table + } + properties := t.getProperties() + if len(properties) == 0 { + return nil + } + members := make(ast.SymbolTable, len(properties)) + for _, member := range properties { + members[member.Name()] = member + } + if t.getLazyState() != nil { + t.members = t.checker.memberIndexArena.New() + t.members.table = members + } + return members +} + +// A memberIndex finds a structured type's members by name. When every member +// is a named property stored under its own name, the property list already +// holds all of them, so larger types index it with pointer-free slots and +// smaller ones need no index at all. Other types keep their symbol table. +type memberIndex struct { + table ast.SymbolTable + slots []uint32 +} + +func (m *memberIndex) get(name string, properties []*ast.Symbol) *ast.Symbol { + if m.table != nil { + return m.table[name] + } + mask := uint64(len(m.slots) - 1) + slot := xxh3.HashString(name) & mask + for { + index := m.slots[slot] + if index == 0 { + return nil + } + if symbol := properties[index-1]; symbol.Name() == name { + return symbol } + slot = (slot + 1) & mask } - return t.members } func (t *StructuredType) getPropertyAt(index int) *ast.Symbol {