Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Check for resolved members before taking the lazy paths
Property and index info lookups are hot, and nearly always see types whose
members are resolved. They now check that inline before calling into the
lazy member table code, which on material-ui's docs project took 2-3% of
check time on its own. The lazy part of getPropertyOfTypeEx moves to
getPropertyOfObjectTypeLazily.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
  • Loading branch information
maschwenk and claude committed Sep 27, 2026
commit af2719bab959105372f85771d33d340d91040eda
40 changes: 12 additions & 28 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -19240,30 +19240,10 @@ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPr
t = c.getReducedApparentType(t)
switch {
case t.flags&TypeFlagsObject != 0:
if symbol, ok := c.lookupMemberLazily(t, name); ok {
if symbol != nil && c.symbolIsValueEx(symbol, includeTypeOnlyMembers) {
if mayHaveLazyMembers(t) {
if symbol, ok := c.getPropertyOfObjectTypeLazily(t, name, skipObjectFunctionPropertyAugment, includeTypeOnlyMembers); ok {
return symbol
}
if skipObjectFunctionPropertyAugment {
return nil
}
if symbol == nil {
if shape := c.getLazyShape(t); shape != nil {
var functionType *Type
switch {
case shape.callSignatureCount != 0:
functionType = c.globalCallableFunctionType
case shape.constructSignatureCount != 0:
functionType = c.globalNewableFunctionType
}
if functionType != nil {
if symbol := c.getPropertyOfObjectType(functionType, name); symbol != nil {
return symbol
}
}
return c.getPropertyOfObjectType(c.globalObjectType, name)
}
}
}
resolved := c.resolveStructuredTypeMembers(t)
symbol := resolved.members[name]
Expand Down Expand Up @@ -19345,8 +19325,10 @@ func (c *Checker) getIndexInfosOfType(t *Type) []*IndexInfo {

func (c *Checker) getIndexInfosOfStructuredType(t *Type) []*IndexInfo {
if t.flags&TypeFlagsStructuredType != 0 {
if indexInfos, ok := c.getIndexInfosLazily(t); ok {
return indexInfos
if mayHaveLazyMembers(t) {
if indexInfos, ok := c.getIndexInfosLazily(t); ok {
return indexInfos
}
}
return c.resolveStructuredTypeMembers(t).indexInfos
}
Expand Down Expand Up @@ -21793,11 +21775,13 @@ func (c *Checker) includeMixinType(t *Type, types []*Type, mixinFlags []bool, in
*/
func (c *Checker) getPropertyOfObjectType(t *Type, name string) *ast.Symbol {
if t.flags&TypeFlagsObject != 0 {
if symbol, ok := c.lookupMemberLazily(t, name); ok {
if symbol != nil && c.symbolIsValue(symbol) {
return symbol
if mayHaveLazyMembers(t) {
if symbol, ok := c.lookupMemberLazily(t, name); ok {
if symbol != nil && c.symbolIsValue(symbol) {
return symbol
}
return nil
}
return nil
}
resolved := c.resolveStructuredTypeMembers(t)
symbol := resolved.members[name]
Expand Down
52 changes: 51 additions & 1 deletion tsc/internal/checker/lazymembers.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type resolvingMembers struct {
// generic class or interface (other than the declaration itself) whose members
// haven't been resolved.
func isUnresolvedInstantiatedReference(t *Type) bool {
if t.flags&TypeFlagsObject == 0 || t.objectFlags&(ObjectFlagsMembersResolved|ObjectFlagsReference) != ObjectFlagsReference {
if t.flags&TypeFlagsObject == 0 || !mayHaveLazyMembers(t) {
return false
}
source := t.Target()
Expand All @@ -84,6 +84,13 @@ func isUnresolvedInstantiatedReference(t *Type) bool {
return t.symbol == nil || t.symbol.Flags&ast.SymbolFlagsValueModule == 0
}

// mayHaveLazyMembers is a quick check for isUnresolvedInstantiatedReference
// that callers on hot paths make first, as most types they see have resolved
// members.
func mayHaveLazyMembers(t *Type) bool {
return t.objectFlags&(ObjectFlagsMembersResolved|ObjectFlagsReference) == ObjectFlagsReference
}

func (c *Checker) getReferenceMemberTypeArguments(t *Type, source *Type) (typeParameters []*Type, typeArguments []*Type) {
typeParameters = source.AsInterfaceType().allTypeParameters
typeArguments = c.getTypeArguments(t)
Expand Down Expand Up @@ -318,6 +325,42 @@ func (c *Checker) lookupMemberLazily(t *Type, name string) (symbol *ast.Symbol,
return result, true
}

// getPropertyOfObjectTypeLazily returns what getPropertyOfTypeEx returns for
// the object type t without resolving its members. ok is false when the lazy
// path can't answer.
func (c *Checker) getPropertyOfObjectTypeLazily(t *Type, name string, skipObjectFunctionPropertyAugment bool, includeTypeOnlyMembers bool) (symbol *ast.Symbol, ok bool) {
symbol, ok = c.lookupMemberLazily(t, name)
if !ok {
return nil, false
}
if symbol != nil && c.symbolIsValueEx(symbol, includeTypeOnlyMembers) {
return symbol, true
}
if skipObjectFunctionPropertyAugment {
return nil, true
}
if symbol != nil {
return nil, false
}
shape := c.getLazyShape(t)
if shape == nil {
return nil, false
}
var functionType *Type
switch {
case shape.callSignatureCount != 0:
functionType = c.globalCallableFunctionType
case shape.constructSignatureCount != 0:
functionType = c.globalNewableFunctionType
}
if functionType != nil {
if symbol := c.getPropertyOfObjectType(functionType, name); symbol != nil {
return symbol, true
}
}
return c.getPropertyOfObjectType(c.globalObjectType, name), true
}

// getIndexInfosLazily returns resolveStructuredTypeMembers(t).indexInfos
// without resolving t's members (e.g. for `arr[i]` on an Array<T>).
func (c *Checker) getIndexInfosLazily(t *Type) ([]*IndexInfo, bool) {
Expand Down Expand Up @@ -370,6 +413,13 @@ func (c *Checker) getNamedPropertyOfType(t *Type, name string) *ast.Symbol {
// of the prepared base types, which resolveObjectTypeMembers concatenates
// (signatures) or merges (properties).
func (c *Checker) getLazyShape(t *Type) *lazyShape {
if !mayHaveLazyMembers(t) {
return nil
}
return c.getLazyShapeWorker(t)
}

func (c *Checker) getLazyShapeWorker(t *Type) *lazyShape {
lm := c.getLazyMemberTable(t)
if lm == nil || lm.state != lazyMembersReady {
return nil
Expand Down