Skip to content
Draft
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
54 changes: 52 additions & 2 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"iter"
"maps"
"math"
"math/bits"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
58 changes: 49 additions & 9 deletions tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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]
Expand All @@ -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 {
Expand Down
Loading