From d52b33f2eee9fbedeb891d173ebf7c15cfb2c06b Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:35:02 -0700 Subject: [PATCH 1/3] Resolve instantiated object members through a shared worker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eagerly instantiating an entire generic member table leaves millions of symbols alive even when checking only needs a few properties. Selective resolution saves memory, but separate lookup and enumeration algorithms would make inheritance, shadowing, and symbol identity harder to preserve. Named lookup, flags-only shape queries, and full enumeration need the same inheritance and shadowing rules. Cached member identities must stay stable when symbol types resolve later. Missing-property lookup must stay bounded in diamond-shaped hierarchies instead of exploring every path. Keeping preparation state after enumeration would waste the memory saved. Measured against upstream/main at aa8149273b2 with six interleaved, fresh-process samples per version. The workload parses, binds, and checks the actual VS Code src/tsconfig.json with one checker, then measures live heap and heap scan bytes after GC while keeping the checker and program alive. No GC settings were changed. VS Code retained heap decreases 6.06%, heap scanning decreases 6.48%, and allocated bytes decrease 3.39%. Allocation events increase 2.83%; this is a real tradeoff, not an allocation-count improvement. Instruction medians decrease, but neither instructions nor elapsed time reach statistical significance on the shared machine, so this does not claim a CPU speedup. Exact benchstat output: goos: linux goarch: amd64 pkg: github.com/microsoft/TypeScript/tsc/internal/checker cpu: Intel(R) Core(TM) i9-10900K CPU @ 3.70GHz │ unified-main-bench.txt │ unified-final-bench.txt │ │ sec/op │ sec/op vs base │ ExternalProject-20 39.12 ± 11% 41.18 ± 8% ~ (p=0.240 n=6) │ unified-main-bench.txt │ unified-final-bench.txt │ │ diagnostics │ diagnostics vs base │ ExternalProject-20 321.0 ± 0% 321.0 ± 0% ~ (p=1.000 n=6) ¹ ¹ all samples are equal │ unified-main-bench.txt │ unified-final-bench.txt │ │ files │ files vs base │ ExternalProject-20 10.28k ± 0% 10.28k ± 0% ~ (p=1.000 n=6) ¹ ¹ all samples are equal │ unified-main-bench.txt │ unified-final-bench.txt │ │ retained-B │ retained-B vs base │ ExternalProject-20 4.996Gi ± 0% 4.693Gi ± 0% -6.06% (p=0.002 n=6) │ unified-main-bench.txt │ unified-final-bench.txt │ │ scan-B │ scan-B vs base │ ExternalProject-20 4.657Gi ± 0% 4.356Gi ± 0% -6.48% (p=0.002 n=6) │ unified-main-bench.txt │ unified-final-bench.txt │ │ symbols │ symbols vs base │ ExternalProject-20 6.385M ± 0% 4.154M ± 0% -34.94% (p=0.002 n=6) │ unified-main-bench.txt │ unified-final-bench.txt │ │ B/op │ B/op vs base │ ExternalProject-20 6.391Gi ± 0% 6.174Gi ± 0% -3.39% (p=0.002 n=6) │ unified-main-bench.txt │ unified-final-bench.txt │ │ allocs/op │ allocs/op vs base │ ExternalProject-20 32.61M ± 0% 33.54M ± 0% +2.83% (p=0.002 n=6) │ unified-main-instructions.txt │ unified-final-instructions.txt │ │ instructions/op │ instructions/op vs base │ ExternalProject-20 322.1G ± 4% 308.8G ± 4% ~ (p=0.093 n=6) The smaller compiler fixture also saves retained heap and allocated bytes, with the same allocation-event tradeoff: │ unified-fixture-main-bench.txt │ unified-fixture-final-bench.txt │ │ retained-B │ retained-B vs base │ ExternalProject-20 217.2Mi ± 0% 212.9Mi ± 0% -1.99% (p=0.002 n=6) │ unified-fixture-main-bench.txt │ unified-fixture-final-bench.txt │ │ B/op │ B/op vs base │ ExternalProject-20 313.9Mi ± 0% 311.9Mi ± 0% -0.66% (p=0.002 n=6) │ unified-fixture-main-bench.txt │ unified-fixture-final-bench.txt │ │ allocs/op │ allocs/op vs base │ ExternalProject-20 1.515M ± 0% 1.536M ± 0% +1.41% (p=0.002 n=6) Compiler diagnostic, declaration, type, and symbol baselines match unchanged main. Coverage includes inheritance, polymorphic this, recursive defaults, reduced bases, index signatures, overlapping generic overloads, lookup-before-enumeration identity, table-key/name differences, negative diamond lookups, and hover/completion queries in either resolution state. The actual VS Code no-emit CLI output is byte-identical to main, including twelve pre-existing diagnostics and exit status 2. Validation passed: npx hereby validate go -C tsc test -race ./internal/checker -run 'TestInstantiated' -count=1 Task-only measurement helpers are not included in this change. --- tsc/internal/checker/checker.go | 291 +++++++++++-- tsc/internal/checker/checker_test.go | 164 +++++++ tsc/internal/checker/relater.go | 4 +- .../completionListForGenericInstance1_test.go | 29 ++ ...tiatedReferenceMemberResolution.errors.txt | 136 ++++++ .../instantiatedReferenceMemberResolution.js | 159 +++++++ ...tantiatedReferenceMemberResolution.symbols | 406 ++++++++++++++++++ ...nstantiatedReferenceMemberResolution.types | 307 +++++++++++++ .../instantiatedReferenceMemberResolution.ts | 92 ++++ 9 files changed, 1543 insertions(+), 45 deletions(-) create mode 100644 tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.js create mode 100644 tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.types create mode 100644 tsc/testdata/tests/cases/compiler/instantiatedReferenceMemberResolution.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 345ea00ce1437..73da66d2fefd2 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -703,6 +703,7 @@ type Checker struct { sourceFileLinks core.LinkStore[*ast.SourceFile, SourceFileLinks] regExpScanner *scanner.Scanner patternForType map[*Type]*ast.Node + objectMemberResolutions map[*Type]*objectMemberResolution contextFreeTypes map[*ast.Node]*Type anyType *Type autoType *Type @@ -983,6 +984,7 @@ func NewChecker(program Program, tracer *Tracer) (*Checker, *sync.Mutex) { c.propertiesTypes = make(map[PropertiesTypesKey]*Type) c.mergedSymbols = make(map[*ast.Symbol]*ast.Symbol) c.patternForType = make(map[*Type]*ast.Node) + c.objectMemberResolutions = make(map[*Type]*objectMemberResolution) c.contextFreeTypes = make(map[*ast.Node]*Type) c.anyType = c.newIntrinsicType(TypeFlagsAny, "any") c.autoType = c.newIntrinsicTypeEx(TypeFlagsAny, "any", ObjectFlagsNonInferrableType) @@ -19316,8 +19318,7 @@ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPr t = c.getReducedApparentType(t) switch { case t.flags&TypeFlagsObject != 0: - resolved := c.resolveStructuredTypeMembers(t) - symbol := resolved.members[name] + symbol := c.getMemberOfStructuredType(t, name) if symbol != nil { if !includeTypeOnlyMembers && t.symbol != nil && t.symbol.Flags()&ast.SymbolFlagsValueModule != 0 && c.moduleSymbolLinks.Get(t.symbol).typeOnlyExportStarMap[name] != nil { // If this is the type of a module, `resolved.members.get(name)` might have effectively skipped over @@ -19336,9 +19337,9 @@ func (c *Checker) getPropertyOfTypeEx(t *Type, name string, skipObjectFunctionPr switch { case t == c.anyFunctionType: functionType = c.globalFunctionType - case len(resolved.CallSignatures()) != 0: + case len(c.getSignaturesOfStructuredType(t, SignatureKindCall)) != 0: functionType = c.globalCallableFunctionType - case len(resolved.ConstructSignatures()) != 0: + case len(c.getSignaturesOfStructuredType(t, SignatureKindConstruct)) != 0: functionType = c.globalNewableFunctionType } if functionType != nil { @@ -19380,7 +19381,7 @@ func (c *Checker) getSignaturesOfStructuredType(t *Type, kind SignatureKind) []* if t.flags&TypeFlagsStructuredType == 0 { return nil } - resolved := c.resolveStructuredTypeMembers(t) + resolved := c.resolveStructuredTypeShape(t) if kind == SignatureKindCall { return resolved.signatures[:resolved.callSignatureCount] } @@ -19393,7 +19394,7 @@ func (c *Checker) getIndexInfosOfType(t *Type) []*IndexInfo { func (c *Checker) getIndexInfosOfStructuredType(t *Type) []*IndexInfo { if t.flags&TypeFlagsStructuredType != 0 { - return c.resolveStructuredTypeMembers(t).indexInfos + return c.resolveStructuredTypeShape(t).indexInfos } return nil } @@ -19520,38 +19521,68 @@ func (c *Checker) resolveTypeReferenceMembers(t *Type) { } func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters []*Type, typeArguments []*Type) { - var mapper *TypeMapper - var members ast.SymbolTable + resolution := c.objectMemberResolutions[t] + if resolution == nil || !resolution.ready { + resolution = &objectMemberResolution{} + c.prepareObjectTypeMembers(t, source, typeParameters, typeArguments, resolution) + } + c.resolveObjectTypeProperties(t, resolution) +} + +// The same resolution selects members for lookups, shape queries, and full enumeration. +// Only instantiated references retain it; fully resolved types retain just the resulting table. +type objectMemberResolution struct { + source *InterfaceType + mapper *TypeMapper + baseTypes []*Type + unaffected []string + members ast.SymbolTable // Includes nil entries for missing members until full resolution. + ready bool + fullOnly bool // Declared table keys differ from symbol names. +} + +func (c *Checker) prepareObjectTypeMembers(t *Type, source *Type, typeParameters []*Type, typeArguments []*Type, resolution *objectMemberResolution) { var callSignatures []*Signature var constructSignatures []*Signature var indexInfos []*IndexInfo - var instantiated bool resolved := c.resolveDeclaredMembers(source) + resolution.source = resolved if slices.Equal(typeParameters, typeArguments) { - members = resolved.declaredMembers callSignatures = resolved.declaredCallSignatures constructSignatures = resolved.declaredConstructSignatures indexInfos = resolved.declaredIndexInfos } else { - instantiated = true - mapper = newTypeMapper(typeParameters, typeArguments) - members = c.instantiateSymbolTable(resolved.declaredMembers, mapper) + mapper := newTypeMapper(typeParameters, typeArguments) + resolution.mapper = mapper + // This decision changes as symbol types resolve. Snapshot it now so lookup order + // cannot change whether a member gets an instantiated symbol. + for name, symbol := range resolved.declaredMembers { + if name != symbol.Name() { + resolution.fullOnly = true + } + if c.isNamedMember(symbol, name) && c.isSymbolUnaffectedByInstantiation(symbol, mapper) { + resolution.unaffected = append(resolution.unaffected, name) + } + } + slices.Sort(resolution.unaffected) callSignatures = c.instantiateSignatures(resolved.declaredCallSignatures, mapper) constructSignatures = c.instantiateSignatures(resolved.declaredConstructSignatures, mapper) indexInfos = c.instantiateIndexInfos(resolved.declaredIndexInfos, mapper) } baseTypes := c.getBaseTypes(source) if len(baseTypes) != 0 { - if !instantiated { - members = maps.Clone(members) - } + resolution.baseTypes = make([]*Type, 0, len(baseTypes)) thisArgument := core.LastOrNil(typeArguments) for _, baseType := range baseTypes { instantiatedBaseType := baseType if thisArgument != nil { - instantiatedBaseType = c.getTypeWithThisArgument(c.instantiateType(baseType, mapper), thisArgument, false /*needsApparentType*/) + instantiatedBaseType = c.getTypeWithThisArgument(c.instantiateType(baseType, resolution.mapper), thisArgument, false /*needsApparentType*/) + } + resolution.baseTypes = append(resolution.baseTypes, instantiatedBaseType) + reduced := c.getReducedApparentType(instantiatedBaseType) + if c.getObjectMemberResolution(reduced) == nil { + c.getPropertiesOfType(instantiatedBaseType) } - members = c.addInheritedMembers(members, c.getPropertiesOfType(instantiatedBaseType)) callSignatures = core.Concatenate(callSignatures, c.getSignaturesOfType(instantiatedBaseType, SignatureKindCall)) constructSignatures = core.Concatenate(constructSignatures, c.getSignaturesOfType(instantiatedBaseType, SignatureKindConstruct)) var inheritedIndexInfos []*IndexInfo @@ -19565,7 +19596,178 @@ func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters })) } } - c.setStructuredTypeMembers(t, members, callSignatures, constructSignatures, indexInfos) + c.setStructuredTypeSignatures(t.AsStructuredType(), callSignatures, constructSignatures, indexInfos) + resolution.ready = true +} + +func (c *Checker) getObjectMemberResolution(t *Type) *objectMemberResolution { + if t.flags&TypeFlagsObject == 0 || t.objectFlags&(ObjectFlagsMembersResolved|ObjectFlagsReference) != ObjectFlagsReference { + return nil + } + source := t.Target() + if source == nil || source == t || source.objectFlags&ObjectFlagsClassOrInterface == 0 || source.objectFlags&ObjectFlagsTuple != 0 || + t.symbol != nil && t.symbol.Flags()&ast.SymbolFlagsValueModule != 0 { + return nil + } + resolution := c.objectMemberResolutions[t] + if resolution == nil { + typeParameters := source.AsInterfaceType().allTypeParameters + typeArguments := c.getTypeArguments(t) + if len(typeArguments) == len(typeParameters)-1 { + typeArguments = core.Concatenate(typeArguments, []*Type{t}) + } + if slices.Equal(typeParameters, typeArguments) { + return nil + } + resolution = &objectMemberResolution{} + c.objectMemberResolutions[t] = resolution + c.prepareObjectTypeMembers(t, source, typeParameters, typeArguments, resolution) + if resolution.fullOnly || t.objectFlags&ObjectFlagsMembersResolved != 0 { + // Reentrant resolution during preparation used a partial base list. + c.resolveObjectTypeProperties(t, resolution) + } + } + if !resolution.ready || t.objectFlags&ObjectFlagsMembersResolved != 0 { + return nil + } + return resolution +} + +func (c *Checker) resolveStructuredTypeShape(t *Type) *StructuredType { + if c.getObjectMemberResolution(t) != nil { + return t.AsStructuredType() + } + return c.resolveStructuredTypeMembers(t) +} + +func (c *Checker) getMemberOfStructuredType(t *Type, name string) *ast.Symbol { + if !isReservedMemberName(name) { + if resolution := c.getObjectMemberResolution(t); resolution != nil { + return c.resolveObjectTypeMember(resolution, name, true /*instantiate*/) + } + } + return c.resolveStructuredTypeMembers(t).members[name] +} + +func (c *Checker) resolveObjectTypeMember(resolution *objectMemberResolution, name string, instantiate bool) *ast.Symbol { + if member, ok := resolution.members[name]; ok { + return member + } + var result *ast.Symbol + if symbol := resolution.source.declaredMembers[name]; symbol != nil { + if resolution.mapper == nil || c.isNamedMember(symbol, name) { + result = symbol + if instantiate && resolution.mapper != nil { + if _, unaffected := slices.BinarySearch(resolution.unaffected, name); !unaffected { + result = c.newInstantiatedSymbol(symbol, resolution.mapper) + } + } + } + } + for _, baseType := range resolution.baseTypes { + if result != nil && result.Flags()&ast.SymbolFlagsValue != 0 { + break + } + var property *ast.Symbol + if baseResolution := c.getObjectMemberResolution(c.getReducedApparentType(baseType)); baseResolution != nil { + property = c.resolveObjectTypeMember(baseResolution, name, instantiate) + if property != nil && !c.isNamedMember(property, name) { + property = nil + } + } else { + property = c.getPropertyOfTypeEx(baseType, name, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) + if property == nil { + // Inherited tables use symbol names, which can differ from the base's keys. + for _, candidate := range c.getPropertiesOfType(baseType) { + if candidate.Name() == name { + property = candidate + break + } + } + } + } + if property != nil && !isStaticPrivateIdentifierProperty(property) { + result = property + } + } + if instantiate || result == nil { + if resolution.members == nil { + resolution.members = make(ast.SymbolTable) + } + resolution.members[name] = result + } + return result +} + +func (c *Checker) everyObjectTypeMemberName(resolution *objectMemberResolution, f func(string) bool) bool { + var seen collections.Set[*objectMemberResolution] + return c.everyObjectTypeMemberNameWorker(resolution, &seen, f) +} + +func (c *Checker) everyObjectTypeMemberNameWorker(resolution *objectMemberResolution, seen *collections.Set[*objectMemberResolution], f func(string) bool) bool { + // A diamond-shaped hierarchy can have exponentially many paths to the same base. + if len(resolution.baseTypes) != 0 && !seen.AddIfAbsent(resolution) { + return true + } + for name := range resolution.source.declaredMembers { + if !f(name) { + return false + } + } + for _, baseType := range resolution.baseTypes { + if baseResolution := c.getObjectMemberResolution(c.getReducedApparentType(baseType)); baseResolution != nil { + if !c.everyObjectTypeMemberNameWorker(baseResolution, seen, f) { + return false + } + } else { + for _, property := range c.getPropertiesOfType(baseType) { + if !f(property.Name()) { + return false + } + } + } + } + return true +} + +func (c *Checker) resolveObjectTypeProperties(t *Type, resolution *objectMemberResolution) { + if resolution.mapper == nil && len(resolution.baseTypes) == 0 { + resolution.members = resolution.source.declaredMembers + } else { + if resolution.members == nil { + resolution.members = make(ast.SymbolTable, len(resolution.source.declaredMembers)) + } + for name := range resolution.source.declaredMembers { + c.resolveObjectTypeMember(resolution, name, true /*instantiate*/) + } + for _, baseType := range resolution.baseTypes { + for _, property := range c.getPropertiesOfType(baseType) { + c.resolveObjectTypeMember(resolution, property.Name(), true /*instantiate*/) + } + } + } + maps.DeleteFunc(resolution.members, func(_ string, symbol *ast.Symbol) bool { return symbol == nil }) + c.setStructuredTypeProperties(t, resolution.members) + delete(c.objectMemberResolutions, t) +} + +func (c *Checker) everyPropertyOfStructuredType(t *Type, f func(*ast.Symbol) bool) bool { + if resolution := c.getObjectMemberResolution(t); resolution != nil { + var seen collections.Set[string] + return c.everyObjectTypeMemberName(resolution, func(name string) bool { + if isReservedMemberName(name) || !seen.AddIfAbsent(name) { + return true + } + // Shape queries only need flags, not instantiated symbol identities. + property := c.resolveObjectTypeMember(resolution, name, false /*instantiate*/) + return property == nil || !c.symbolIsValue(property) || f(property) + }) + } + return core.Every(c.resolveStructuredTypeMembers(t).properties, f) +} + +func (c *Checker) hasPropertiesOfStructuredType(t *Type) bool { + return !c.everyPropertyOfStructuredType(t, func(*ast.Symbol) bool { return false }) } func findIndexInfo(indexInfos []*IndexInfo, keyType *Type) *IndexInfo { @@ -19765,8 +19967,8 @@ func (c *Checker) getSingleCallOrConstructSignature(t *Type) *Signature { func (c *Checker) getSingleSignature(t *Type, kind SignatureKind, allowMembers bool) *Signature { if t.flags&TypeFlagsObject != 0 { - resolved := c.resolveStructuredTypeMembers(t) - if allowMembers || len(resolved.properties) == 0 && len(resolved.indexInfos) == 0 { + resolved := c.resolveStructuredTypeShape(t) + if allowMembers || !c.hasPropertiesOfStructuredType(t) && len(resolved.indexInfos) == 0 { if kind == SignatureKindCall && len(resolved.CallSignatures()) == 1 && len(resolved.ConstructSignatures()) == 0 { return resolved.CallSignatures()[0] } @@ -21150,38 +21352,34 @@ func (c *Checker) createInstantiatedSymbolTable(symbols []*ast.Symbol, m *TypeMa return result } -func (c *Checker) instantiateSymbolTable(symbols ast.SymbolTable, m *TypeMapper) ast.SymbolTable { - if len(symbols) == 0 { - return nil - } - result := make(ast.SymbolTable, len(symbols)) - for id, symbol := range symbols { - if c.isNamedMember(symbol, id) { - result[id] = c.instantiateSymbol(symbol, m) - } +func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symbol { + if symbol == nil || c.isSymbolUnaffectedByInstantiation(symbol, m) { + return symbol } - return result + return c.newInstantiatedSymbol(symbol, m) } -func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symbol { - if symbol == nil { - return nil - } +func (c *Checker) isSymbolUnaffectedByInstantiation(symbol *ast.Symbol, m *TypeMapper) bool { links := c.valueSymbolLinks.Get(symbol) if m != nil && m.MapsThisOnly() && isThisless(symbol) { - return symbol + return true } // If the type of the symbol is already resolved, and if that type could not possibly // be affected by instantiation, simply return the symbol itself. if links.resolvedType != nil && !c.couldContainTypeVariables(links.resolvedType) { if symbol.Flags()&ast.SymbolFlagsSetAccessor == 0 { - return symbol + return true } // If we're a setter, check writeType. if links.writeType != nil && !c.couldContainTypeVariables(links.writeType) { - return symbol + return true } } + return false +} + +func (c *Checker) newInstantiatedSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symbol { + links := c.valueSymbolLinks.Get(symbol) if symbol.CheckFlags()&ast.CheckFlagsInstantiated != 0 { // If symbol being instantiated is itself a instantiation, fetch the original target and combine the // type mappers. This ensures that original type identities are properly preserved and that aliases @@ -21811,8 +22009,7 @@ 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 { - resolved := c.resolveStructuredTypeMembers(t) - symbol := resolved.members[name] + symbol := c.getMemberOfStructuredType(t, name) if symbol != nil && c.symbolIsValue(symbol) { return symbol } @@ -25682,10 +25879,18 @@ func (c *Checker) cloneTypeReference(source *Type) *Type { } func (c *Checker) setStructuredTypeMembers(t *Type, members ast.SymbolTable, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { + c.setStructuredTypeProperties(t, members) + c.setStructuredTypeSignatures(t.AsStructuredType(), callSignatures, constructSignatures, indexInfos) +} + +func (c *Checker) setStructuredTypeProperties(t *Type, members ast.SymbolTable) { t.objectFlags |= ObjectFlagsMembersResolved data := t.AsStructuredType() data.members = members data.properties = c.getNamedMembers(members, t.symbol) +} + +func (c *Checker) setStructuredTypeSignatures(data *StructuredType, callSignatures []*Signature, constructSignatures []*Signature, indexInfos []*IndexInfo) { if len(callSignatures) != 0 { if len(constructSignatures) != 0 { data.signatures = core.Concatenate(callSignatures, constructSignatures) @@ -27902,7 +28107,7 @@ func (c *Checker) getPropertyNameFromIndex(indexType *Type, accessNode *ast.Node } func (c *Checker) isStringIndexSignatureOnlyTypeWorker(t *Type) bool { - return t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && len(c.getPropertiesOfType(t)) == 0 && len(c.getIndexInfosOfType(t)) == 1 && c.getIndexInfoOfType(t, c.stringType) != nil || + return t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && !c.hasPropertiesOfStructuredType(t) && len(c.getIndexInfosOfType(t)) == 1 && c.getIndexInfoOfType(t, c.stringType) != nil || t.flags&TypeFlagsUnionOrIntersection != 0 && core.Every(t.Types(), c.isStringIndexSignatureOnlyType) } @@ -31697,8 +31902,8 @@ func (c *Checker) isFunctionObjectType(t *Type) bool { } // We do a quick check for a "bind" property before performing the more expensive subtype // check. This gives us a quicker out in the common case where an object type is not a function. - resolved := c.resolveStructuredTypeMembers(t) - return len(resolved.signatures) != 0 || resolved.members["bind"] != nil && c.isTypeSubtypeOf(t, c.globalFunctionType) + resolved := c.resolveStructuredTypeShape(t) + return len(resolved.signatures) != 0 || c.getMemberOfStructuredType(t, "bind") != nil && c.isTypeSubtypeOf(t, c.globalFunctionType) } func (c *Checker) getTypeWithFacts(t *Type, include TypeFacts) *Type { diff --git a/tsc/internal/checker/checker_test.go b/tsc/internal/checker/checker_test.go index 2ed189904d64b..14fa31bd7514b 100644 --- a/tsc/internal/checker/checker_test.go +++ b/tsc/internal/checker/checker_test.go @@ -1,7 +1,9 @@ package checker_test import ( + "fmt" "path/filepath" + "strings" "testing" "github.com/microsoft/TypeScript/tsc/internal/ast" @@ -115,6 +117,168 @@ export type E = D;`, assert.Equal(t, defaultClause, defaultReference) } +func TestInstantiatedMemberLookupIdentity(t *testing.T) { + t.Parallel() + for _, reverse := range []bool{false, true} { + t.Run(core.IfElse(reverse, "reverse", "forward"), func(t *testing.T) { + t.Parallel() + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/main.ts": `interface Base { inherited: T; shared: string } +interface Derived extends Base { own: T; shared: "derived"; unused: string } +declare const instance: Derived; +instance.inherited; +instance.own; +instance.shared;`, + "/tsconfig.json": `{"compilerOptions":{"strict":true},"files":["main.ts"]}`, + }, tspath.CaseInsensitive)) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(t, len(errors), 0) + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + c, done := program.GetTypeChecker(t.Context()) + defer done() + file := program.GetSourceFile("/main.ts") + expressions := []*ast.Node{ + file.Statements.Nodes[3].Expression(), + file.Statements.Nodes[4].Expression(), + file.Statements.Nodes[5].Expression(), + } + symbols := make([]*ast.Symbol, len(expressions)) + typ := c.GetTypeAtLocation(expressions[0].Expression()) + for index := range expressions { + i := index + if reverse { + i = len(expressions) - index - 1 + } + symbols[i] = c.GetPropertyOfType(typ, expressions[i].Name().Text()) + assert.Assert(t, symbols[i] != nil) + } + before := c.SymbolCount + for _, expression := range expressions { + c.GetPropertyOfType(typ, expression.Name().Text()) + } + assert.Equal(t, c.SymbolCount, before) + unused := file.Statements.Nodes[1].Symbol().Members()["unused"] + c.GetTypeOfSymbol(unused) + properties := c.GetPropertiesOfType(typ) + assert.Equal(t, len(properties), 4) + assert.Equal(t, properties[0].Name(), "own") + assert.Equal(t, properties[1].Name(), "shared") + assert.Equal(t, properties[2].Name(), "unused") + assert.Assert(t, properties[2] != unused) + assert.Equal(t, properties[3].Name(), "inherited") + for i, expression := range expressions { + assert.Equal(t, c.GetPropertyOfType(typ, expression.Name().Text()), symbols[i]) + assert.Equal(t, c.GetSymbolAtLocation(expression), symbols[i]) + found := false + for _, property := range properties { + if property == symbols[i] { + found = true + } + } + assert.Assert(t, found) + } + assert.Equal(t, len(c.GetDiagnostics(t.Context(), file)), 0) + }) + } +} + +func TestInstantiatedMemberShapeQueries(t *testing.T) { + t.Parallel() + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/main.ts": `interface Container { + (): T; + [key: string]: T; + value: T; + unused: T; + another: T; +} +declare const container: Container; +container.value;`, + "/tsconfig.json": `{"compilerOptions":{"strict":true},"files":["main.ts"]}`, + }, tspath.CaseInsensitive)) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(t, len(errors), 0) + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + c, done := program.GetTypeChecker(t.Context()) + defer done() + file := program.GetSourceFile("/main.ts") + typ := c.GetTypeAtLocation(file.Statements.Nodes[2].Expression().Expression()) + before := c.SymbolCount + value := c.GetPropertyOfType(typ, "value") + assert.Assert(t, value != nil) + assert.Equal(t, c.SymbolCount, before+1) + before = c.SymbolCount + assert.Equal(t, len(c.GetSignaturesOfType(typ, checker.SignatureKindCall)), 1) + assert.Equal(t, len(c.GetIndexInfosOfType(typ)), 1) + assert.Equal(t, c.SymbolCount, before) + properties := c.GetPropertiesOfType(typ) + assert.Equal(t, len(properties), 3) + assert.Equal(t, c.SymbolCount, before+2) + assert.Equal(t, c.GetPropertyOfType(typ, "value"), value) +} + +func TestInstantiatedMemberTableKeys(t *testing.T) { + t.Parallel() + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/main.ts": `interface Base { value: T } +interface Derived extends Base {} +declare const instance: Derived; +instance;`, + "/tsconfig.json": `{"files":["main.ts"]}`, + }, tspath.CaseInsensitive)) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(t, len(errors), 0) + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + file := program.GetSourceFile("/main.ts") + members := file.Statements.Nodes[0].Symbol().Members() + members["different"] = members["value"] + delete(members, "value") + c, done := program.GetTypeChecker(t.Context()) + defer done() + typ := c.GetTypeAtLocation(file.Statements.Nodes[3].Expression()) + value := c.GetPropertyOfType(typ, "value") + assert.Assert(t, value != nil) + properties := c.GetPropertiesOfType(typ) + assert.Equal(t, len(properties), 1) + assert.Equal(t, properties[0], value) + assert.Assert(t, c.GetPropertyOfType(typ, "different") == nil) +} + +func TestInstantiatedDiamondMissingMember(t *testing.T) { + t.Parallel() + var source strings.Builder + source.WriteString("interface L0 { value?: T }\ninterface R0 { value?: T }\n") + for i := 1; i <= 24; i++ { + fmt.Fprintf(&source, "interface L%d extends L%d, R%d {}\ninterface R%d extends L%d, R%d {}\n", i, i-1, i-1, i, i-1, i-1) + } + source.WriteString("declare const instance: L24;\ninstance;") + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/main.ts": source.String(), + "/tsconfig.json": `{"files":["main.ts"]}`, + }, tspath.CaseInsensitive)) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(t, len(errors), 0) + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + c, done := program.GetTypeChecker(t.Context()) + defer done() + file := program.GetSourceFile("/main.ts") + typ := c.GetTypeAtLocation(file.Statements.Nodes[len(file.Statements.Nodes)-1].Expression()) + assert.Assert(t, c.GetPropertyOfType(typ, "missing") == nil) + value := c.GetPropertyOfType(typ, "value") + assert.Assert(t, value != nil) + properties := c.GetPropertiesOfType(typ) + assert.Equal(t, len(properties), 1) + assert.Equal(t, properties[0], value) +} + func BenchmarkNewChecker(b *testing.B) { fs := bundled.WrapFS(osvfs.FS()) rootPath := tspath.RootedDirectoryPathFromAbsolute(filepath.Join(repo.TestDataPath(), "fixtures/compiler")) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index a9bc484fef1d2..863b7e9c9015b 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -678,8 +678,8 @@ func (c *Checker) elaborateArrowFunction(node *ast.Node, source *Type, target *T // and no required properties, call/construct signatures or index signatures func (c *Checker) isWeakType(t *Type) bool { if t.flags&TypeFlagsObject != 0 { - resolved := c.resolveStructuredTypeMembers(t) - return len(resolved.signatures) == 0 && len(resolved.indexInfos) == 0 && len(resolved.properties) > 0 && core.Every(resolved.properties, func(p *ast.Symbol) bool { + resolved := c.resolveStructuredTypeShape(t) + return len(resolved.signatures) == 0 && len(resolved.indexInfos) == 0 && c.hasPropertiesOfStructuredType(t) && c.everyPropertyOfStructuredType(t, func(p *ast.Symbol) bool { return p.Flags()&ast.SymbolFlagsOptional != 0 }) } diff --git a/tsc/internal/fourslash/tests/completionListForGenericInstance1_test.go b/tsc/internal/fourslash/tests/completionListForGenericInstance1_test.go index 767d6a6059a1c..8d3ac18c5c382 100644 --- a/tsc/internal/fourslash/tests/completionListForGenericInstance1_test.go +++ b/tsc/internal/fourslash/tests/completionListForGenericInstance1_test.go @@ -36,3 +36,32 @@ i/**/` }, }) } + +func TestGenericMemberLookupBeforeAndAfterCompletion(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +interface Base { inherited: T; shared: string } +interface Derived extends Base { own: T; shared: "derived" } +declare const instance: Derived; +instance./*inherited*/inherited; +instance./*shared*/shared; +instance./*completion*/ +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyQuickInfoAt(t, "inherited", "(property) Base.inherited: number[]", "") + f.VerifyQuickInfoAt(t, "shared", `(property) Derived.shared: "derived"`, "") + f.VerifyCompletions(t, "completion", &fourslash.CompletionsExpectedList{ + IsIncomplete: false, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Exact: []fourslash.CompletionsExpectedItem{"inherited", "own", "shared"}, + }, + }) + f.VerifyQuickInfoAt(t, "inherited", "(property) Base.inherited: number[]", "") + f.VerifyQuickInfoAt(t, "shared", `(property) Derived.shared: "derived"`, "") +} diff --git a/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.errors.txt b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.errors.txt new file mode 100644 index 0000000000000..3fd893faa9af5 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.errors.txt @@ -0,0 +1,136 @@ +instantiatedReferenceMemberResolution.ts(14,9): error TS2339: Property 'missing' does not exist on type 'Derived'. +instantiatedReferenceMemberResolution.ts(28,5): error TS2341: Property 'secret' is private and only accessible within class 'Box'. +instantiatedReferenceMemberResolution.ts(34,7): error TS2559: Type '{ other: number; }' has no properties in common with type 'Weak'. +instantiatedReferenceMemberResolution.ts(37,7): error TS2559: Type '{ other: number; }' has no properties in common with type 'ConditionalBase<"b">'. +instantiatedReferenceMemberResolution.ts(38,7): error TS2741: Property 'kind' is missing in type '{ other: number; }' but required in type 'ConditionalBase<"a">'. +instantiatedReferenceMemberResolution.ts(43,9): error TS2339: Property 'bind' does not exist on type 'Fn<"b">'. +instantiatedReferenceMemberResolution.ts(50,11): error TS2430: Interface 'Wrap' incorrectly extends interface 'T'. + 'Wrap' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ a?: string | undefined; }'. +instantiatedReferenceMemberResolution.ts(51,7): error TS2741: Property 'b' is missing in type '{ other: number; }' but required in type 'Wrap<{ a?: string | undefined; b: number; }>'. +instantiatedReferenceMemberResolution.ts(59,24): error TS2716: Type parameter 'T' has a circular default. +instantiatedReferenceMemberResolution.ts(74,7): error TS2322: Type 'ReducedDict<"b">' is not assignable to type '{ [key: string]: number; }'. + 'string' index signatures are incompatible. + Type 'unknown' is not assignable to type 'number'. +instantiatedReferenceMemberResolution.ts(80,7): error TS2322: Type '(x: U, y: string) => U' is not assignable to type 'number'. +instantiatedReferenceMemberResolution.ts(82,7): error TS2322: Type '(x: unknown, y: string) => unknown' is not assignable to type 'number'. + + +==== instantiatedReferenceMemberResolution.ts (12 errors) ==== + interface Base { + value: T; + shared: string; + } + interface Derived extends Base { + shared: "derived"; + own: T; + } + declare const derived: Derived; + export const value = derived.value; + export const shared = derived.shared; + export const properties = { ...derived }; + export const again = derived.value; + derived.missing; + ~~~~~~~ +!!! error TS2339: Property 'missing' does not exist on type 'Derived'. + + class Box { + private secret!: T; + protected hidden!: T; + contents!: T; + get readonlyContents(): T { return this.contents; } + set writeonlyContents(value: T) {} + method(value: U): [T, U] { return [this.contents, value]; } + } + declare const box: Box; + export const contents = box.contents; + export const method = box.method(1); + export const boxProperties = { ...box }; + box.secret; + ~~~~~~ +!!! error TS2341: Property 'secret' is private and only accessible within class 'Box'. + + interface Weak { + value?: T; + } + declare const unrelated: { other: number }; + const weak: Weak = unrelated; + ~~~~ +!!! error TS2559: Type '{ other: number; }' has no properties in common with type 'Weak'. + type Tagged = { kind: T } & { kind: "a" }; + interface ConditionalBase extends Tagged { optional?: T } + const reduced: ConditionalBase<"b"> = unrelated; + ~~~~~~~ +!!! error TS2559: Type '{ other: number; }' has no properties in common with type 'ConditionalBase<"b">'. + const unreduced: ConditionalBase<"a"> = unrelated; + ~~~~~~~~~ +!!! error TS2741: Property 'kind' is missing in type '{ other: number; }' but required in type 'ConditionalBase<"a">'. +!!! related TS2728 instantiatedReferenceMemberResolution.ts:35:20: 'kind' is declared here. + + type Callable = { (): T; kind: T } & { kind: "a" }; + interface Fn extends Callable {} + declare const neverFn: Fn<"b">; + neverFn.bind; + ~~~~ +!!! error TS2339: Property 'bind' does not exist on type 'Fn<"b">'. + declare const fn: Fn<"a">; + export const bound = fn.bind; + interface Dict { [key: string]: T } + declare const dict: Dict; + export const indexed = dict["property"]; + + interface Wrap extends T {} + ~~~~ +!!! error TS2430: Interface 'Wrap' incorrectly extends interface 'T'. +!!! error TS2430: 'Wrap' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ a?: string | undefined; }'. + const wrapped: Wrap<{ a?: string; b: number }> = unrelated; + ~~~~~~~ +!!! error TS2741: Property 'b' is missing in type '{ other: number; }' but required in type 'Wrap<{ a?: string | undefined; b: number; }>'. +!!! related TS2728 instantiatedReferenceMemberResolution.ts:51:35: 'b' is declared here. + + interface Tree extends Array> { value: T } + declare const tree: Tree; + export const children = tree.map(child => child.value); + interface Self { next: this; value: T } + declare const self: Self; + export const next = self.next.next.value; + interface Defaults { value: T } + ~~~~~~~~ +!!! error TS2716: Type parameter 'T' has a circular default. + declare const defaults: Defaults; + export const defaultValue = defaults.value; + + interface First { shared: T } + interface Second { shared: T; second: T } + interface Multiple extends First, Second { own: T } + declare const multiple: Multiple; + export const multipleValue = multiple.shared; + export const multipleProperties = { ...multiple }; + + interface ReducedDict extends Tagged { + [key: string]: unknown; + } + declare const reducedDict: ReducedDict<"b">; + const numberDict: { [key: string]: number } = reducedDict; + ~~~~~~~~~~ +!!! error TS2322: Type 'ReducedDict<"b">' is not assignable to type '{ [key: string]: number; }'. +!!! error TS2322: 'string' index signatures are incompatible. +!!! error TS2322: Type 'unknown' is not assignable to type 'number'. + + type Two = { (x: U, y: T): U } & { (x: U, y: string): U }; + interface Overloads extends Two {} + declare function pipe(f: (...args: A) => B): (...args: A) => B; + declare const merged: Overloads; + const piped1: number = pipe(merged); + ~~~~~~ +!!! error TS2322: Type '(x: U, y: string) => U' is not assignable to type 'number'. + declare const distinct: Overloads; + const piped2: number = pipe(distinct); + ~~~~~~ +!!! error TS2322: Type '(x: unknown, y: string) => unknown' is not assignable to type 'number'. + + interface CallableBox { (): T } + declare const boxOrCallable: CallableBox | Box; + if (typeof boxOrCallable === "function") { + boxOrCallable; + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.js b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.js new file mode 100644 index 0000000000000..914f09c119dea --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.js @@ -0,0 +1,159 @@ +//// [tests/cases/compiler/instantiatedReferenceMemberResolution.ts] //// + +//// [instantiatedReferenceMemberResolution.ts] +interface Base { + value: T; + shared: string; +} +interface Derived extends Base { + shared: "derived"; + own: T; +} +declare const derived: Derived; +export const value = derived.value; +export const shared = derived.shared; +export const properties = { ...derived }; +export const again = derived.value; +derived.missing; + +class Box { + private secret!: T; + protected hidden!: T; + contents!: T; + get readonlyContents(): T { return this.contents; } + set writeonlyContents(value: T) {} + method(value: U): [T, U] { return [this.contents, value]; } +} +declare const box: Box; +export const contents = box.contents; +export const method = box.method(1); +export const boxProperties = { ...box }; +box.secret; + +interface Weak { + value?: T; +} +declare const unrelated: { other: number }; +const weak: Weak = unrelated; +type Tagged = { kind: T } & { kind: "a" }; +interface ConditionalBase extends Tagged { optional?: T } +const reduced: ConditionalBase<"b"> = unrelated; +const unreduced: ConditionalBase<"a"> = unrelated; + +type Callable = { (): T; kind: T } & { kind: "a" }; +interface Fn extends Callable {} +declare const neverFn: Fn<"b">; +neverFn.bind; +declare const fn: Fn<"a">; +export const bound = fn.bind; +interface Dict { [key: string]: T } +declare const dict: Dict; +export const indexed = dict["property"]; + +interface Wrap extends T {} +const wrapped: Wrap<{ a?: string; b: number }> = unrelated; + +interface Tree extends Array> { value: T } +declare const tree: Tree; +export const children = tree.map(child => child.value); +interface Self { next: this; value: T } +declare const self: Self; +export const next = self.next.next.value; +interface Defaults { value: T } +declare const defaults: Defaults; +export const defaultValue = defaults.value; + +interface First { shared: T } +interface Second { shared: T; second: T } +interface Multiple extends First, Second { own: T } +declare const multiple: Multiple; +export const multipleValue = multiple.shared; +export const multipleProperties = { ...multiple }; + +interface ReducedDict extends Tagged { + [key: string]: unknown; +} +declare const reducedDict: ReducedDict<"b">; +const numberDict: { [key: string]: number } = reducedDict; + +type Two = { (x: U, y: T): U } & { (x: U, y: string): U }; +interface Overloads extends Two {} +declare function pipe(f: (...args: A) => B): (...args: A) => B; +declare const merged: Overloads; +const piped1: number = pipe(merged); +declare const distinct: Overloads; +const piped2: number = pipe(distinct); + +interface CallableBox { (): T } +declare const boxOrCallable: CallableBox | Box; +if (typeof boxOrCallable === "function") { + boxOrCallable; +} + + +//// [instantiatedReferenceMemberResolution.js] +export const value = derived.value; +export const shared = derived.shared; +export const properties = { ...derived }; +export const again = derived.value; +derived.missing; +class Box { + secret; + hidden; + contents; + get readonlyContents() { return this.contents; } + set writeonlyContents(value) { } + method(value) { return [this.contents, value]; } +} +export const contents = box.contents; +export const method = box.method(1); +export const boxProperties = { ...box }; +box.secret; +const weak = unrelated; +const reduced = unrelated; +const unreduced = unrelated; +neverFn.bind; +export const bound = fn.bind; +export const indexed = dict["property"]; +const wrapped = unrelated; +export const children = tree.map(child => child.value); +export const next = self.next.next.value; +export const defaultValue = defaults.value; +export const multipleValue = multiple.shared; +export const multipleProperties = { ...multiple }; +const numberDict = reducedDict; +const piped1 = pipe(merged); +const piped2 = pipe(distinct); +if (typeof boxOrCallable === "function") { + boxOrCallable; +} + + +//// [instantiatedReferenceMemberResolution.d.ts] +export declare const value: number[]; +export declare const shared: "derived"; +export declare const properties: { + value: number[]; + shared: "derived"; + own: number; +}; +export declare const again: number[]; +export declare const contents: string; +export declare const method: [string, number]; +export declare const boxProperties: { + contents: string; +}; +export declare const bound: { + (this: T, thisArg: ThisParameterType): OmitThisParameter; + (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; +}; +export declare const indexed: string; +export declare const children: string[]; +export declare const next: number; +export declare const defaultValue: unknown; +export declare const multipleValue: number; +export declare const multipleProperties: { + shared: number; + second: number; + own: number; +}; diff --git a/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.symbols b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.symbols new file mode 100644 index 0000000000000..442d7786ee51a --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.symbols @@ -0,0 +1,406 @@ +//// [tests/cases/compiler/instantiatedReferenceMemberResolution.ts] //// + +=== instantiatedReferenceMemberResolution.ts === +interface Base { +>Base : Symbol(Base, Decl(instantiatedReferenceMemberResolution.ts, 0, 0)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 0, 15)) + + value: T; +>value : Symbol(Base.value, Decl(instantiatedReferenceMemberResolution.ts, 0, 19)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 0, 15)) + + shared: string; +>shared : Symbol(Base.shared, Decl(instantiatedReferenceMemberResolution.ts, 1, 13)) +} +interface Derived extends Base { +>Derived : Symbol(Derived, Decl(instantiatedReferenceMemberResolution.ts, 3, 1)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 4, 18)) +>Base : Symbol(Base, Decl(instantiatedReferenceMemberResolution.ts, 0, 0)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 4, 18)) + + shared: "derived"; +>shared : Symbol(Derived.shared, Decl(instantiatedReferenceMemberResolution.ts, 4, 40)) + + own: T; +>own : Symbol(Derived.own, Decl(instantiatedReferenceMemberResolution.ts, 5, 22)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 4, 18)) +} +declare const derived: Derived; +>derived : Symbol(derived, Decl(instantiatedReferenceMemberResolution.ts, 8, 13)) +>Derived : Symbol(Derived, Decl(instantiatedReferenceMemberResolution.ts, 3, 1)) + +export const value = derived.value; +>value : Symbol(value, Decl(instantiatedReferenceMemberResolution.ts, 9, 12)) +>derived.value : Symbol(Base.value, Decl(instantiatedReferenceMemberResolution.ts, 0, 19)) +>derived : Symbol(derived, Decl(instantiatedReferenceMemberResolution.ts, 8, 13)) +>value : Symbol(Base.value, Decl(instantiatedReferenceMemberResolution.ts, 0, 19)) + +export const shared = derived.shared; +>shared : Symbol(shared, Decl(instantiatedReferenceMemberResolution.ts, 10, 12)) +>derived.shared : Symbol(Derived.shared, Decl(instantiatedReferenceMemberResolution.ts, 4, 40)) +>derived : Symbol(derived, Decl(instantiatedReferenceMemberResolution.ts, 8, 13)) +>shared : Symbol(Derived.shared, Decl(instantiatedReferenceMemberResolution.ts, 4, 40)) + +export const properties = { ...derived }; +>properties : Symbol(properties, Decl(instantiatedReferenceMemberResolution.ts, 11, 12)) +>derived : Symbol(derived, Decl(instantiatedReferenceMemberResolution.ts, 8, 13)) + +export const again = derived.value; +>again : Symbol(again, Decl(instantiatedReferenceMemberResolution.ts, 12, 12)) +>derived.value : Symbol(Base.value, Decl(instantiatedReferenceMemberResolution.ts, 0, 19)) +>derived : Symbol(derived, Decl(instantiatedReferenceMemberResolution.ts, 8, 13)) +>value : Symbol(Base.value, Decl(instantiatedReferenceMemberResolution.ts, 0, 19)) + +derived.missing; +>derived : Symbol(derived, Decl(instantiatedReferenceMemberResolution.ts, 8, 13)) + +class Box { +>Box : Symbol(Box, Decl(instantiatedReferenceMemberResolution.ts, 13, 16)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) + + private secret!: T; +>secret : Symbol(Box.secret, Decl(instantiatedReferenceMemberResolution.ts, 15, 14)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) + + protected hidden!: T; +>hidden : Symbol(Box.hidden, Decl(instantiatedReferenceMemberResolution.ts, 16, 23)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) + + contents!: T; +>contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) + + get readonlyContents(): T { return this.contents; } +>readonlyContents : Symbol(Box.readonlyContents, Decl(instantiatedReferenceMemberResolution.ts, 18, 17)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) +>this.contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) +>this : Symbol(Box, Decl(instantiatedReferenceMemberResolution.ts, 13, 16)) +>contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) + + set writeonlyContents(value: T) {} +>writeonlyContents : Symbol(Box.writeonlyContents, Decl(instantiatedReferenceMemberResolution.ts, 19, 55)) +>value : Symbol(value, Decl(instantiatedReferenceMemberResolution.ts, 20, 26)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) + + method(value: U): [T, U] { return [this.contents, value]; } +>method : Symbol(Box.method, Decl(instantiatedReferenceMemberResolution.ts, 20, 38)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 21, 11)) +>value : Symbol(value, Decl(instantiatedReferenceMemberResolution.ts, 21, 14)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 21, 11)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 15, 10)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 21, 11)) +>this.contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) +>this : Symbol(Box, Decl(instantiatedReferenceMemberResolution.ts, 13, 16)) +>contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) +>value : Symbol(value, Decl(instantiatedReferenceMemberResolution.ts, 21, 14)) +} +declare const box: Box; +>box : Symbol(box, Decl(instantiatedReferenceMemberResolution.ts, 23, 13)) +>Box : Symbol(Box, Decl(instantiatedReferenceMemberResolution.ts, 13, 16)) + +export const contents = box.contents; +>contents : Symbol(contents, Decl(instantiatedReferenceMemberResolution.ts, 24, 12)) +>box.contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) +>box : Symbol(box, Decl(instantiatedReferenceMemberResolution.ts, 23, 13)) +>contents : Symbol(Box.contents, Decl(instantiatedReferenceMemberResolution.ts, 17, 25)) + +export const method = box.method(1); +>method : Symbol(method, Decl(instantiatedReferenceMemberResolution.ts, 25, 12)) +>box.method : Symbol(Box.method, Decl(instantiatedReferenceMemberResolution.ts, 20, 38)) +>box : Symbol(box, Decl(instantiatedReferenceMemberResolution.ts, 23, 13)) +>method : Symbol(Box.method, Decl(instantiatedReferenceMemberResolution.ts, 20, 38)) + +export const boxProperties = { ...box }; +>boxProperties : Symbol(boxProperties, Decl(instantiatedReferenceMemberResolution.ts, 26, 12)) +>box : Symbol(box, Decl(instantiatedReferenceMemberResolution.ts, 23, 13)) + +box.secret; +>box.secret : Symbol(Box.secret, Decl(instantiatedReferenceMemberResolution.ts, 15, 14)) +>box : Symbol(box, Decl(instantiatedReferenceMemberResolution.ts, 23, 13)) +>secret : Symbol(Box.secret, Decl(instantiatedReferenceMemberResolution.ts, 15, 14)) + +interface Weak { +>Weak : Symbol(Weak, Decl(instantiatedReferenceMemberResolution.ts, 27, 11)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 29, 15)) + + value?: T; +>value : Symbol(Weak.value, Decl(instantiatedReferenceMemberResolution.ts, 29, 19)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 29, 15)) +} +declare const unrelated: { other: number }; +>unrelated : Symbol(unrelated, Decl(instantiatedReferenceMemberResolution.ts, 32, 13)) +>other : Symbol(other, Decl(instantiatedReferenceMemberResolution.ts, 32, 26)) + +const weak: Weak = unrelated; +>weak : Symbol(weak, Decl(instantiatedReferenceMemberResolution.ts, 33, 5)) +>Weak : Symbol(Weak, Decl(instantiatedReferenceMemberResolution.ts, 27, 11)) +>unrelated : Symbol(unrelated, Decl(instantiatedReferenceMemberResolution.ts, 32, 13)) + +type Tagged = { kind: T } & { kind: "a" }; +>Tagged : Symbol(Tagged, Decl(instantiatedReferenceMemberResolution.ts, 33, 37)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 34, 12)) +>kind : Symbol(kind, Decl(instantiatedReferenceMemberResolution.ts, 34, 18)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 34, 12)) +>kind : Symbol(kind, Decl(instantiatedReferenceMemberResolution.ts, 34, 32)) + +interface ConditionalBase extends Tagged { optional?: T } +>ConditionalBase : Symbol(ConditionalBase, Decl(instantiatedReferenceMemberResolution.ts, 34, 45)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 35, 26)) +>Tagged : Symbol(Tagged, Decl(instantiatedReferenceMemberResolution.ts, 33, 37)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 35, 26)) +>optional : Symbol(ConditionalBase.optional, Decl(instantiatedReferenceMemberResolution.ts, 35, 63)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 35, 26)) + +const reduced: ConditionalBase<"b"> = unrelated; +>reduced : Symbol(reduced, Decl(instantiatedReferenceMemberResolution.ts, 36, 5)) +>ConditionalBase : Symbol(ConditionalBase, Decl(instantiatedReferenceMemberResolution.ts, 34, 45)) +>unrelated : Symbol(unrelated, Decl(instantiatedReferenceMemberResolution.ts, 32, 13)) + +const unreduced: ConditionalBase<"a"> = unrelated; +>unreduced : Symbol(unreduced, Decl(instantiatedReferenceMemberResolution.ts, 37, 5)) +>ConditionalBase : Symbol(ConditionalBase, Decl(instantiatedReferenceMemberResolution.ts, 34, 45)) +>unrelated : Symbol(unrelated, Decl(instantiatedReferenceMemberResolution.ts, 32, 13)) + +type Callable = { (): T; kind: T } & { kind: "a" }; +>Callable : Symbol(Callable, Decl(instantiatedReferenceMemberResolution.ts, 37, 50)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 39, 14)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 39, 14)) +>kind : Symbol(kind, Decl(instantiatedReferenceMemberResolution.ts, 39, 27)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 39, 14)) +>kind : Symbol(kind, Decl(instantiatedReferenceMemberResolution.ts, 39, 41)) + +interface Fn extends Callable {} +>Fn : Symbol(Fn, Decl(instantiatedReferenceMemberResolution.ts, 39, 54)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 40, 13)) +>Callable : Symbol(Callable, Decl(instantiatedReferenceMemberResolution.ts, 37, 50)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 40, 13)) + +declare const neverFn: Fn<"b">; +>neverFn : Symbol(neverFn, Decl(instantiatedReferenceMemberResolution.ts, 41, 13)) +>Fn : Symbol(Fn, Decl(instantiatedReferenceMemberResolution.ts, 39, 54)) + +neverFn.bind; +>neverFn : Symbol(neverFn, Decl(instantiatedReferenceMemberResolution.ts, 41, 13)) + +declare const fn: Fn<"a">; +>fn : Symbol(fn, Decl(instantiatedReferenceMemberResolution.ts, 43, 13)) +>Fn : Symbol(Fn, Decl(instantiatedReferenceMemberResolution.ts, 39, 54)) + +export const bound = fn.bind; +>bound : Symbol(bound, Decl(instantiatedReferenceMemberResolution.ts, 44, 12)) +>fn.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>fn : Symbol(fn, Decl(instantiatedReferenceMemberResolution.ts, 43, 13)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +interface Dict { [key: string]: T } +>Dict : Symbol(Dict, Decl(instantiatedReferenceMemberResolution.ts, 44, 29)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 45, 15)) +>key : Symbol(key, Decl(instantiatedReferenceMemberResolution.ts, 45, 21)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 45, 15)) + +declare const dict: Dict; +>dict : Symbol(dict, Decl(instantiatedReferenceMemberResolution.ts, 46, 13)) +>Dict : Symbol(Dict, Decl(instantiatedReferenceMemberResolution.ts, 44, 29)) + +export const indexed = dict["property"]; +>indexed : Symbol(indexed, Decl(instantiatedReferenceMemberResolution.ts, 47, 12)) +>dict : Symbol(dict, Decl(instantiatedReferenceMemberResolution.ts, 46, 13)) + +interface Wrap extends T {} +>Wrap : Symbol(Wrap, Decl(instantiatedReferenceMemberResolution.ts, 47, 40)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 49, 15)) +>a : Symbol(a, Decl(instantiatedReferenceMemberResolution.ts, 49, 26)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 49, 15)) + +const wrapped: Wrap<{ a?: string; b: number }> = unrelated; +>wrapped : Symbol(wrapped, Decl(instantiatedReferenceMemberResolution.ts, 50, 5)) +>Wrap : Symbol(Wrap, Decl(instantiatedReferenceMemberResolution.ts, 47, 40)) +>a : Symbol(a, Decl(instantiatedReferenceMemberResolution.ts, 50, 21)) +>b : Symbol(b, Decl(instantiatedReferenceMemberResolution.ts, 50, 33)) +>unrelated : Symbol(unrelated, Decl(instantiatedReferenceMemberResolution.ts, 32, 13)) + +interface Tree extends Array> { value: T } +>Tree : Symbol(Tree, Decl(instantiatedReferenceMemberResolution.ts, 50, 59)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 52, 15)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>Tree : Symbol(Tree, Decl(instantiatedReferenceMemberResolution.ts, 50, 59)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 52, 15)) +>value : Symbol(Tree.value, Decl(instantiatedReferenceMemberResolution.ts, 52, 42)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 52, 15)) + +declare const tree: Tree; +>tree : Symbol(tree, Decl(instantiatedReferenceMemberResolution.ts, 53, 13)) +>Tree : Symbol(Tree, Decl(instantiatedReferenceMemberResolution.ts, 50, 59)) + +export const children = tree.map(child => child.value); +>children : Symbol(children, Decl(instantiatedReferenceMemberResolution.ts, 54, 12)) +>tree.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>tree : Symbol(tree, Decl(instantiatedReferenceMemberResolution.ts, 53, 13)) +>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>child : Symbol(child, Decl(instantiatedReferenceMemberResolution.ts, 54, 33)) +>child.value : Symbol(Tree.value, Decl(instantiatedReferenceMemberResolution.ts, 52, 42)) +>child : Symbol(child, Decl(instantiatedReferenceMemberResolution.ts, 54, 33)) +>value : Symbol(Tree.value, Decl(instantiatedReferenceMemberResolution.ts, 52, 42)) + +interface Self { next: this; value: T } +>Self : Symbol(Self, Decl(instantiatedReferenceMemberResolution.ts, 54, 55)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 55, 15)) +>next : Symbol(Self.next, Decl(instantiatedReferenceMemberResolution.ts, 55, 19)) +>value : Symbol(Self.value, Decl(instantiatedReferenceMemberResolution.ts, 55, 31)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 55, 15)) + +declare const self: Self; +>self : Symbol(self, Decl(instantiatedReferenceMemberResolution.ts, 56, 13)) +>Self : Symbol(Self, Decl(instantiatedReferenceMemberResolution.ts, 54, 55)) + +export const next = self.next.next.value; +>next : Symbol(next, Decl(instantiatedReferenceMemberResolution.ts, 57, 12)) +>self.next.next.value : Symbol(Self.value, Decl(instantiatedReferenceMemberResolution.ts, 55, 31)) +>self.next.next : Symbol(Self.next, Decl(instantiatedReferenceMemberResolution.ts, 55, 19)) +>self.next : Symbol(Self.next, Decl(instantiatedReferenceMemberResolution.ts, 55, 19)) +>self : Symbol(self, Decl(instantiatedReferenceMemberResolution.ts, 56, 13)) +>next : Symbol(Self.next, Decl(instantiatedReferenceMemberResolution.ts, 55, 19)) +>next : Symbol(Self.next, Decl(instantiatedReferenceMemberResolution.ts, 55, 19)) +>value : Symbol(Self.value, Decl(instantiatedReferenceMemberResolution.ts, 55, 31)) + +interface Defaults { value: T } +>Defaults : Symbol(Defaults, Decl(instantiatedReferenceMemberResolution.ts, 57, 41)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 58, 19)) +>Defaults : Symbol(Defaults, Decl(instantiatedReferenceMemberResolution.ts, 57, 41)) +>value : Symbol(Defaults.value, Decl(instantiatedReferenceMemberResolution.ts, 58, 34)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 58, 19)) + +declare const defaults: Defaults; +>defaults : Symbol(defaults, Decl(instantiatedReferenceMemberResolution.ts, 59, 13)) +>Defaults : Symbol(Defaults, Decl(instantiatedReferenceMemberResolution.ts, 57, 41)) + +export const defaultValue = defaults.value; +>defaultValue : Symbol(defaultValue, Decl(instantiatedReferenceMemberResolution.ts, 60, 12)) +>defaults.value : Symbol(Defaults.value, Decl(instantiatedReferenceMemberResolution.ts, 58, 34)) +>defaults : Symbol(defaults, Decl(instantiatedReferenceMemberResolution.ts, 59, 13)) +>value : Symbol(Defaults.value, Decl(instantiatedReferenceMemberResolution.ts, 58, 34)) + +interface First { shared: T } +>First : Symbol(First, Decl(instantiatedReferenceMemberResolution.ts, 60, 43)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 62, 16)) +>shared : Symbol(First.shared, Decl(instantiatedReferenceMemberResolution.ts, 62, 20)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 62, 16)) + +interface Second { shared: T; second: T } +>Second : Symbol(Second, Decl(instantiatedReferenceMemberResolution.ts, 62, 32)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 63, 17)) +>shared : Symbol(Second.shared, Decl(instantiatedReferenceMemberResolution.ts, 63, 21)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 63, 17)) +>second : Symbol(Second.second, Decl(instantiatedReferenceMemberResolution.ts, 63, 32)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 63, 17)) + +interface Multiple extends First, Second { own: T } +>Multiple : Symbol(Multiple, Decl(instantiatedReferenceMemberResolution.ts, 63, 44)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 64, 19)) +>First : Symbol(First, Decl(instantiatedReferenceMemberResolution.ts, 60, 43)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 64, 19)) +>Second : Symbol(Second, Decl(instantiatedReferenceMemberResolution.ts, 62, 32)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 64, 19)) +>own : Symbol(Multiple.own, Decl(instantiatedReferenceMemberResolution.ts, 64, 51)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 64, 19)) + +declare const multiple: Multiple; +>multiple : Symbol(multiple, Decl(instantiatedReferenceMemberResolution.ts, 65, 13)) +>Multiple : Symbol(Multiple, Decl(instantiatedReferenceMemberResolution.ts, 63, 44)) + +export const multipleValue = multiple.shared; +>multipleValue : Symbol(multipleValue, Decl(instantiatedReferenceMemberResolution.ts, 66, 12)) +>multiple.shared : Symbol(First.shared, Decl(instantiatedReferenceMemberResolution.ts, 62, 20)) +>multiple : Symbol(multiple, Decl(instantiatedReferenceMemberResolution.ts, 65, 13)) +>shared : Symbol(First.shared, Decl(instantiatedReferenceMemberResolution.ts, 62, 20)) + +export const multipleProperties = { ...multiple }; +>multipleProperties : Symbol(multipleProperties, Decl(instantiatedReferenceMemberResolution.ts, 67, 12)) +>multiple : Symbol(multiple, Decl(instantiatedReferenceMemberResolution.ts, 65, 13)) + +interface ReducedDict extends Tagged { +>ReducedDict : Symbol(ReducedDict, Decl(instantiatedReferenceMemberResolution.ts, 67, 50)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 69, 22)) +>Tagged : Symbol(Tagged, Decl(instantiatedReferenceMemberResolution.ts, 33, 37)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 69, 22)) + + [key: string]: unknown; +>key : Symbol(key, Decl(instantiatedReferenceMemberResolution.ts, 70, 5)) +} +declare const reducedDict: ReducedDict<"b">; +>reducedDict : Symbol(reducedDict, Decl(instantiatedReferenceMemberResolution.ts, 72, 13)) +>ReducedDict : Symbol(ReducedDict, Decl(instantiatedReferenceMemberResolution.ts, 67, 50)) + +const numberDict: { [key: string]: number } = reducedDict; +>numberDict : Symbol(numberDict, Decl(instantiatedReferenceMemberResolution.ts, 73, 5)) +>key : Symbol(key, Decl(instantiatedReferenceMemberResolution.ts, 73, 21)) +>reducedDict : Symbol(reducedDict, Decl(instantiatedReferenceMemberResolution.ts, 72, 13)) + +type Two = { (x: U, y: T): U } & { (x: U, y: string): U }; +>Two : Symbol(Two, Decl(instantiatedReferenceMemberResolution.ts, 73, 58)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 75, 9)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 75, 17)) +>x : Symbol(x, Decl(instantiatedReferenceMemberResolution.ts, 75, 20)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 75, 17)) +>y : Symbol(y, Decl(instantiatedReferenceMemberResolution.ts, 75, 25)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 75, 9)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 75, 17)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 75, 42)) +>x : Symbol(x, Decl(instantiatedReferenceMemberResolution.ts, 75, 45)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 75, 42)) +>y : Symbol(y, Decl(instantiatedReferenceMemberResolution.ts, 75, 50)) +>U : Symbol(U, Decl(instantiatedReferenceMemberResolution.ts, 75, 42)) + +interface Overloads extends Two {} +>Overloads : Symbol(Overloads, Decl(instantiatedReferenceMemberResolution.ts, 75, 67)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 76, 20)) +>Two : Symbol(Two, Decl(instantiatedReferenceMemberResolution.ts, 73, 58)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 76, 20)) + +declare function pipe(f: (...args: A) => B): (...args: A) => B; +>pipe : Symbol(pipe, Decl(instantiatedReferenceMemberResolution.ts, 76, 40)) +>A : Symbol(A, Decl(instantiatedReferenceMemberResolution.ts, 77, 22)) +>B : Symbol(B, Decl(instantiatedReferenceMemberResolution.ts, 77, 38)) +>f : Symbol(f, Decl(instantiatedReferenceMemberResolution.ts, 77, 42)) +>args : Symbol(args, Decl(instantiatedReferenceMemberResolution.ts, 77, 46)) +>A : Symbol(A, Decl(instantiatedReferenceMemberResolution.ts, 77, 22)) +>B : Symbol(B, Decl(instantiatedReferenceMemberResolution.ts, 77, 38)) +>args : Symbol(args, Decl(instantiatedReferenceMemberResolution.ts, 77, 66)) +>A : Symbol(A, Decl(instantiatedReferenceMemberResolution.ts, 77, 22)) +>B : Symbol(B, Decl(instantiatedReferenceMemberResolution.ts, 77, 38)) + +declare const merged: Overloads; +>merged : Symbol(merged, Decl(instantiatedReferenceMemberResolution.ts, 78, 13)) +>Overloads : Symbol(Overloads, Decl(instantiatedReferenceMemberResolution.ts, 75, 67)) + +const piped1: number = pipe(merged); +>piped1 : Symbol(piped1, Decl(instantiatedReferenceMemberResolution.ts, 79, 5)) +>pipe : Symbol(pipe, Decl(instantiatedReferenceMemberResolution.ts, 76, 40)) +>merged : Symbol(merged, Decl(instantiatedReferenceMemberResolution.ts, 78, 13)) + +declare const distinct: Overloads; +>distinct : Symbol(distinct, Decl(instantiatedReferenceMemberResolution.ts, 80, 13)) +>Overloads : Symbol(Overloads, Decl(instantiatedReferenceMemberResolution.ts, 75, 67)) + +const piped2: number = pipe(distinct); +>piped2 : Symbol(piped2, Decl(instantiatedReferenceMemberResolution.ts, 81, 5)) +>pipe : Symbol(pipe, Decl(instantiatedReferenceMemberResolution.ts, 76, 40)) +>distinct : Symbol(distinct, Decl(instantiatedReferenceMemberResolution.ts, 80, 13)) + +interface CallableBox { (): T } +>CallableBox : Symbol(CallableBox, Decl(instantiatedReferenceMemberResolution.ts, 81, 38)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 83, 22)) +>T : Symbol(T, Decl(instantiatedReferenceMemberResolution.ts, 83, 22)) + +declare const boxOrCallable: CallableBox | Box; +>boxOrCallable : Symbol(boxOrCallable, Decl(instantiatedReferenceMemberResolution.ts, 84, 13)) +>CallableBox : Symbol(CallableBox, Decl(instantiatedReferenceMemberResolution.ts, 81, 38)) +>Box : Symbol(Box, Decl(instantiatedReferenceMemberResolution.ts, 13, 16)) + +if (typeof boxOrCallable === "function") { +>boxOrCallable : Symbol(boxOrCallable, Decl(instantiatedReferenceMemberResolution.ts, 84, 13)) + + boxOrCallable; +>boxOrCallable : Symbol(boxOrCallable, Decl(instantiatedReferenceMemberResolution.ts, 84, 13)) +} + diff --git a/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.types b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.types new file mode 100644 index 0000000000000..706225322e6ed --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/instantiatedReferenceMemberResolution.types @@ -0,0 +1,307 @@ +//// [tests/cases/compiler/instantiatedReferenceMemberResolution.ts] //// + +=== instantiatedReferenceMemberResolution.ts === +interface Base { + value: T; +>value : T + + shared: string; +>shared : string +} +interface Derived extends Base { + shared: "derived"; +>shared : "derived" + + own: T; +>own : T +} +declare const derived: Derived; +>derived : Derived + +export const value = derived.value; +>value : number[] +>derived.value : number[] +>derived : Derived +>value : number[] + +export const shared = derived.shared; +>shared : "derived" +>derived.shared : "derived" +>derived : Derived +>shared : "derived" + +export const properties = { ...derived }; +>properties : { value: number[]; shared: "derived"; own: number; } +>{ ...derived } : { value: number[]; shared: "derived"; own: number; } +>derived : Derived + +export const again = derived.value; +>again : number[] +>derived.value : number[] +>derived : Derived +>value : number[] + +derived.missing; +>derived.missing : any +>derived : Derived +>missing : any + +class Box { +>Box : Box + + private secret!: T; +>secret : T + + protected hidden!: T; +>hidden : T + + contents!: T; +>contents : T + + get readonlyContents(): T { return this.contents; } +>readonlyContents : T +>this.contents : T +>this : this +>contents : T + + set writeonlyContents(value: T) {} +>writeonlyContents : T +>value : T + + method(value: U): [T, U] { return [this.contents, value]; } +>method : (value: U) => [T, U] +>value : U +>[this.contents, value] : [T, U] +>this.contents : T +>this : this +>contents : T +>value : U +} +declare const box: Box; +>box : Box + +export const contents = box.contents; +>contents : string +>box.contents : string +>box : Box +>contents : string + +export const method = box.method(1); +>method : [string, number] +>box.method(1) : [string, number] +>box.method : (value: U) => [string, U] +>box : Box +>method : (value: U) => [string, U] +>1 : 1 + +export const boxProperties = { ...box }; +>boxProperties : { contents: string; } +>{ ...box } : { contents: string; } +>box : Box + +box.secret; +>box.secret : string +>box : Box +>secret : string + +interface Weak { + value?: T; +>value : T | undefined +} +declare const unrelated: { other: number }; +>unrelated : { other: number; } +>other : number + +const weak: Weak = unrelated; +>weak : Weak +>unrelated : { other: number; } + +type Tagged = { kind: T } & { kind: "a" }; +>Tagged : Tagged +>kind : T +>kind : "a" + +interface ConditionalBase extends Tagged { optional?: T } +>optional : T | undefined + +const reduced: ConditionalBase<"b"> = unrelated; +>reduced : ConditionalBase<"b"> +>unrelated : { other: number; } + +const unreduced: ConditionalBase<"a"> = unrelated; +>unreduced : ConditionalBase<"a"> +>unrelated : { other: number; } + +type Callable = { (): T; kind: T } & { kind: "a" }; +>Callable : Callable +>kind : T +>kind : "a" + +interface Fn extends Callable {} +declare const neverFn: Fn<"b">; +>neverFn : Fn<"b"> + +neverFn.bind; +>neverFn.bind : any +>neverFn : Fn<"b"> +>bind : any + +declare const fn: Fn<"a">; +>fn : Fn<"a"> + +export const bound = fn.bind; +>bound : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>fn.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>fn : Fn<"a"> +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } + +interface Dict { [key: string]: T } +>key : string + +declare const dict: Dict; +>dict : Dict + +export const indexed = dict["property"]; +>indexed : string +>dict["property"] : string +>dict : Dict +>"property" : "property" + +interface Wrap extends T {} +>a : string | undefined + +const wrapped: Wrap<{ a?: string; b: number }> = unrelated; +>wrapped : Wrap<{ a?: string; b: number; }> +>a : string | undefined +>b : number +>unrelated : { other: number; } + +interface Tree extends Array> { value: T } +>value : T + +declare const tree: Tree; +>tree : Tree + +export const children = tree.map(child => child.value); +>children : string[] +>tree.map(child => child.value) : string[] +>tree.map : (callbackfn: (value: Tree, index: number, array: Tree[]) => U, thisArg?: any) => U[] +>tree : Tree +>map : (callbackfn: (value: Tree, index: number, array: Tree[]) => U, thisArg?: any) => U[] +>child => child.value : (child: Tree) => string +>child : Tree +>child.value : string +>child : Tree +>value : string + +interface Self { next: this; value: T } +>next : this +>value : T + +declare const self: Self; +>self : Self + +export const next = self.next.next.value; +>next : number +>self.next.next.value : number +>self.next.next : Self +>self.next : Self +>self : Self +>next : Self +>next : Self +>value : number + +interface Defaults { value: T } +>value : T + +declare const defaults: Defaults; +>defaults : Defaults + +export const defaultValue = defaults.value; +>defaultValue : unknown +>defaults.value : unknown +>defaults : Defaults +>value : unknown + +interface First { shared: T } +>shared : T + +interface Second { shared: T; second: T } +>shared : T +>second : T + +interface Multiple extends First, Second { own: T } +>own : T + +declare const multiple: Multiple; +>multiple : Multiple + +export const multipleValue = multiple.shared; +>multipleValue : number +>multiple.shared : number +>multiple : Multiple +>shared : number + +export const multipleProperties = { ...multiple }; +>multipleProperties : { shared: number; second: number; own: number; } +>{ ...multiple } : { shared: number; second: number; own: number; } +>multiple : Multiple + +interface ReducedDict extends Tagged { + [key: string]: unknown; +>key : string +} +declare const reducedDict: ReducedDict<"b">; +>reducedDict : ReducedDict<"b"> + +const numberDict: { [key: string]: number } = reducedDict; +>numberDict : { [key: string]: number; } +>key : string +>reducedDict : ReducedDict<"b"> + +type Two = { (x: U, y: T): U } & { (x: U, y: string): U }; +>Two : Two +>x : U +>y : T +>x : U +>y : string + +interface Overloads extends Two {} +declare function pipe(f: (...args: A) => B): (...args: A) => B; +>pipe : (f: (...args: A) => B) => (...args: A) => B +>f : (...args: A) => B +>args : A +>args : A + +declare const merged: Overloads; +>merged : Overloads + +const piped1: number = pipe(merged); +>piped1 : number +>pipe(merged) : (x: U, y: string) => U +>pipe : (f: (...args: A) => B) => (...args: A) => B +>merged : Overloads + +declare const distinct: Overloads; +>distinct : Overloads + +const piped2: number = pipe(distinct); +>piped2 : number +>pipe(distinct) : (x: unknown, y: string) => unknown +>pipe : (f: (...args: A) => B) => (...args: A) => B +>distinct : Overloads + +interface CallableBox { (): T } +declare const boxOrCallable: CallableBox | Box; +>boxOrCallable : Box | CallableBox + +if (typeof boxOrCallable === "function") { +>typeof boxOrCallable === "function" : boolean +>typeof boxOrCallable : "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" +>boxOrCallable : Box | CallableBox +>"function" : "function" + + boxOrCallable; +>boxOrCallable : CallableBox +} + diff --git a/tsc/testdata/tests/cases/compiler/instantiatedReferenceMemberResolution.ts b/tsc/testdata/tests/cases/compiler/instantiatedReferenceMemberResolution.ts new file mode 100644 index 0000000000000..c57505f1dc726 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/instantiatedReferenceMemberResolution.ts @@ -0,0 +1,92 @@ +// @strict: true +// @target: esnext +// @declaration: true + +interface Base { + value: T; + shared: string; +} +interface Derived extends Base { + shared: "derived"; + own: T; +} +declare const derived: Derived; +export const value = derived.value; +export const shared = derived.shared; +export const properties = { ...derived }; +export const again = derived.value; +derived.missing; + +class Box { + private secret!: T; + protected hidden!: T; + contents!: T; + get readonlyContents(): T { return this.contents; } + set writeonlyContents(value: T) {} + method(value: U): [T, U] { return [this.contents, value]; } +} +declare const box: Box; +export const contents = box.contents; +export const method = box.method(1); +export const boxProperties = { ...box }; +box.secret; + +interface Weak { + value?: T; +} +declare const unrelated: { other: number }; +const weak: Weak = unrelated; +type Tagged = { kind: T } & { kind: "a" }; +interface ConditionalBase extends Tagged { optional?: T } +const reduced: ConditionalBase<"b"> = unrelated; +const unreduced: ConditionalBase<"a"> = unrelated; + +type Callable = { (): T; kind: T } & { kind: "a" }; +interface Fn extends Callable {} +declare const neverFn: Fn<"b">; +neverFn.bind; +declare const fn: Fn<"a">; +export const bound = fn.bind; +interface Dict { [key: string]: T } +declare const dict: Dict; +export const indexed = dict["property"]; + +interface Wrap extends T {} +const wrapped: Wrap<{ a?: string; b: number }> = unrelated; + +interface Tree extends Array> { value: T } +declare const tree: Tree; +export const children = tree.map(child => child.value); +interface Self { next: this; value: T } +declare const self: Self; +export const next = self.next.next.value; +interface Defaults { value: T } +declare const defaults: Defaults; +export const defaultValue = defaults.value; + +interface First { shared: T } +interface Second { shared: T; second: T } +interface Multiple extends First, Second { own: T } +declare const multiple: Multiple; +export const multipleValue = multiple.shared; +export const multipleProperties = { ...multiple }; + +interface ReducedDict extends Tagged { + [key: string]: unknown; +} +declare const reducedDict: ReducedDict<"b">; +const numberDict: { [key: string]: number } = reducedDict; + +type Two = { (x: U, y: T): U } & { (x: U, y: string): U }; +interface Overloads extends Two {} +declare function pipe(f: (...args: A) => B): (...args: A) => B; +declare const merged: Overloads; +const piped1: number = pipe(merged); +declare const distinct: Overloads; +const piped2: number = pipe(distinct); + +interface CallableBox { (): T } +declare const boxOrCallable: CallableBox | Box; +if (typeof boxOrCallable === "function") { + boxOrCallable; +} From 7a67716eb3a6400c5a65fb953baa7a2e79693d04 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:16:39 -0700 Subject: [PATCH 2/3] Avoid rediscovering members during full enumeration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full enumeration already supplies the declared and inherited symbols. Repeating selective lookup for each one adds declared-table probes and base traversal that are useful for single-property queries but redundant for the complete property list. Bulk resolution still needs the preparation-time instantiation decision, cached symbol identity and misses, and the same precedence rules as lazy lookup. Inherited members with unusual keys or aliases, and module or reentrant bases, must retain the selective fallback instead of assuming an enumerated value is the lookup result. Adapted from local commit 72f191f758095abc0ec32fb8e15230c9fa35d3f2. This branch has ordinary symbol tables, not compact member indexes: direct property publication retains a permanent symbol table, and an inherited value qualifies only when the base table contains that exact symbol at its name. No compact-index representation changes are imported. Measured against this branch at d52b33f2eee, not against the separately optimized data-oriented-compiler branch. Six fresh-process interleaved production CLI samples per version use default threading and GC. Every before/after sample has byte-identical diagnostic text and identical Files, Lines, Identifiers, Symbols, Types, and Instantiations counts. The supplied CI-pinned MUI archive reduces instructions 3.15% and local check time 3.07%, both p=0.002. Xstate instructions and check time are inconclusive; CLI-reported heap decreases 0.81%. Neither workload has a statistically significant allocation-event change. Reported heap is not a forced-GC retained-heap measurement. Xstate uses the supplied partial dependency installation and emits pre-existing errors; it is not a clean-build benchmark. Local timing on the shared host does not prove recovery of hosted CI timing. Verbatim benchstat output: │ mui-pinned-before.bench │ mui-pinned-after.bench │ │ instructions/op │ instructions/op vs base │ mui-pinned-20 333.9G ± 1% 323.4G ± 3% -3.15% (p=0.002 n=6) │ mui-pinned-before.bench │ mui-pinned-after.bench │ │ heap-B/op │ heap-B/op vs base │ mui-pinned-20 2.654Gi ± 0% 2.653Gi ± 0% ~ (p=0.818 n=6) │ mui-pinned-before.bench │ mui-pinned-after.bench │ │ allocs/op │ allocs/op vs base │ mui-pinned-20 33.41M ± 1% 33.38M ± 0% ~ (p=0.394 n=6) │ mui-pinned-before.bench │ mui-pinned-after.bench │ │ check-sec/op │ check-sec/op vs base │ mui-pinned-20 8.649 ± 1% 8.384 ± 2% -3.07% (p=0.002 n=6) │ xstate-before.bench │ xstate-after.bench │ │ instructions/op │ instructions/op vs base │ xstate-20 24.10G ± 8% 23.92G ± 8% ~ (p=0.818 n=6) │ xstate-before.bench │ xstate-after.bench │ │ heap-B/op │ heap-B/op vs base │ xstate-20 360.8Mi ± 0% 357.8Mi ± 0% -0.81% (p=0.002 n=6) │ xstate-before.bench │ xstate-after.bench │ │ allocs/op │ allocs/op vs base │ xstate-20 3.974M ± 1% 3.979M ± 0% ~ (p=0.065 n=6) │ xstate-before.bench │ xstate-after.bench │ │ check-sec/op │ check-sec/op vs base │ xstate-20 854.0m ± 4% 848.0m ± 3% ~ (p=0.937 n=6) Added wide declared/inherited enumeration coverage with and without prior lookups, including symbol identity, cached misses, ordering, and first-base provenance. Existing renamed-key and instantiation-snapshot tests pass. No compiler or editor baselines changed. Validation completed without error: npx hereby test go -C tsc test ./internal/checker -run '^TestInstantiated' \ -count=1 -race go -C tsc test ./internal/checker -run '^TestInstantiated' \ -count=1 -gcflags=all=-d=checkptr=2 npx hereby validate --all Full validation passed 166112 Go tests (2145 skipped), 954 API tests, tooling tests, builds, formatting, and linting. --- tsc/internal/checker/checker.go | 67 +++++++++++++++++++++++++--- tsc/internal/checker/checker_test.go | 59 ++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 7 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 73da66d2fefd2..d3f88b0d6ab36 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -19658,9 +19658,7 @@ func (c *Checker) resolveObjectTypeMember(resolution *objectMemberResolution, na if resolution.mapper == nil || c.isNamedMember(symbol, name) { result = symbol if instantiate && resolution.mapper != nil { - if _, unaffected := slices.BinarySearch(resolution.unaffected, name); !unaffected { - result = c.newInstantiatedSymbol(symbol, resolution.mapper) - } + result = c.instantiateObjectTypeMember(resolution, name, symbol) } } } @@ -19699,6 +19697,15 @@ func (c *Checker) resolveObjectTypeMember(resolution *objectMemberResolution, na return result } +func (c *Checker) instantiateObjectTypeMember(resolution *objectMemberResolution, name string, symbol *ast.Symbol) *ast.Symbol { + if resolution.mapper != nil { + if _, unaffected := slices.BinarySearch(resolution.unaffected, name); !unaffected { + return c.newInstantiatedSymbol(symbol, resolution.mapper) + } + } + return symbol +} + func (c *Checker) everyObjectTypeMemberName(resolution *objectMemberResolution, f func(string) bool) bool { var seen collections.Set[*objectMemberResolution] return c.everyObjectTypeMemberNameWorker(resolution, &seen, f) @@ -19731,18 +19738,64 @@ func (c *Checker) everyObjectTypeMemberNameWorker(resolution *objectMemberResolu } func (c *Checker) resolveObjectTypeProperties(t *Type, resolution *objectMemberResolution) { + if resolution.mapper != nil && len(resolution.baseTypes) == 0 && !resolution.fullOnly { + properties := c.getNamedMembers(resolution.source.declaredMembers, t.symbol) + members := make(ast.SymbolTable, len(properties)) + count := 0 + for _, symbol := range properties { + name := symbol.Name() + member, cached := resolution.members[name] + if !cached { + member = c.instantiateObjectTypeMember(resolution, name, symbol) + } + if member != nil { + members[name] = member + properties[count] = member + count++ + } + } + data := t.AsStructuredType() + data.members = members + data.properties = properties[:count] + t.objectFlags |= ObjectFlagsMembersResolved + delete(c.objectMemberResolutions, t) + return + } if resolution.mapper == nil && len(resolution.baseTypes) == 0 { resolution.members = resolution.source.declaredMembers } else { if resolution.members == nil { resolution.members = make(ast.SymbolTable, len(resolution.source.declaredMembers)) } - for name := range resolution.source.declaredMembers { - c.resolveObjectTypeMember(resolution, name, true /*instantiate*/) + for name, symbol := range resolution.source.declaredMembers { + if _, cached := resolution.members[name]; cached { + continue + } + if symbol.Flags()&ast.SymbolFlagsValue != 0 && (resolution.mapper == nil || !isReservedMemberName(name)) { + resolution.members[name] = c.instantiateObjectTypeMember(resolution, name, symbol) + } else { + c.resolveObjectTypeMember(resolution, name, true /*instantiate*/) + } } for _, baseType := range resolution.baseTypes { - for _, property := range c.getPropertiesOfType(baseType) { - c.resolveObjectTypeMember(resolution, property.Name(), true /*instantiate*/) + properties := c.getPropertiesOfType(baseType) + base := c.getReducedApparentType(baseType) + canonical := base.flags&TypeFlagsObject != 0 && base.objectFlags&ObjectFlagsMembersResolved != 0 && + c.objectMemberResolutions[base] == nil && + (base.symbol == nil || base.symbol.Flags()&ast.SymbolFlagsValueModule == 0) + for _, property := range properties { + name := property.Name() + if _, cached := resolution.members[name]; cached { + continue + } + // Reuse the enumerated value only when lookup through the base's table + // has the same identity; unusual keys and aliases use the shared resolver. + if canonical && base.AsStructuredType().members[name] == property && + property.Flags()&ast.SymbolFlagsValue != 0 && !isStaticPrivateIdentifierProperty(property) { + resolution.members[name] = property + } else { + c.resolveObjectTypeMember(resolution, name, true /*instantiate*/) + } } } } diff --git a/tsc/internal/checker/checker_test.go b/tsc/internal/checker/checker_test.go index 14fa31bd7514b..0024f269dd338 100644 --- a/tsc/internal/checker/checker_test.go +++ b/tsc/internal/checker/checker_test.go @@ -250,6 +250,65 @@ instance;`, assert.Assert(t, c.GetPropertyOfType(typ, "different") == nil) } +func TestInstantiatedMemberEnumeration(t *testing.T) { + t.Parallel() + var source strings.Builder + source.WriteString("interface Base { shared: T; ") + for i := range 64 { + fmt.Fprintf(&source, "p%d: T; ", i) + } + source.WriteString("}\ninterface First extends Base { first: T }\n") + source.WriteString("interface Second { shared: T; second: T }\n") + source.WriteString("interface Derived extends First, Second { own: T }\n") + for _, test := range []struct { + name string + typeName string + count int + first string + partial bool + }{ + {name: "declared", typeName: "Base", count: 65, first: "shared"}, + {name: "declaredCached", typeName: "Base", count: 65, first: "shared", partial: true}, + {name: "inherited", typeName: "Derived", count: 68, first: "own"}, + {name: "inheritedCached", typeName: "Derived", count: 68, first: "own", partial: true}, + } { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + fs := bundled.WrapFS(vfstest.FromMap(map[string]string{ + "/main.ts": source.String() + fmt.Sprintf("declare const instance: %s;\ninstance;", test.typeName), + "/tsconfig.json": `{"compilerOptions":{"strict":true},"files":["main.ts"]}`, + }, tspath.CaseInsensitive)) + host := compiler.NewCompilerHost(fs, bundled.LibPath(), nil, nil, nil) + parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, fs, nil) + assert.Equal(t, len(errors), 0) + program := compiler.NewProgram(compiler.ProgramOptions{Config: parsed, Host: host}) + program.BindSourceFiles() + c, done := program.GetTypeChecker(t.Context()) + defer done() + file := program.GetSourceFile("/main.ts") + typ := c.GetTypeAtLocation(file.Statements.Nodes[5].Expression()) + cached := make(map[string]*ast.Symbol) + if test.partial { + for _, name := range []string{"p63", "shared", "second", "missing"} { + cached[name] = c.GetPropertyOfType(typ, name) + } + } + properties := c.GetPropertiesOfType(typ) + assert.Equal(t, len(properties), test.count) + assert.Equal(t, properties[0].Name(), test.first) + assert.Equal(t, c.GetPropertyOfType(typ, "shared").ValueDeclaration(), file.Statements.Nodes[0].Symbol().Members()["shared"].ValueDeclaration()) + for _, property := range properties { + assert.Equal(t, c.GetPropertyOfType(typ, property.Name()), property) + assert.Equal(t, c.TypeToString(c.GetTypeOfSymbol(property)), "string") + } + for name, symbol := range cached { + assert.Equal(t, c.GetPropertyOfType(typ, name), symbol) + } + assert.Equal(t, len(c.GetDiagnostics(t.Context(), file)), 0) + }) + } +} + func TestInstantiatedDiamondMissingMember(t *testing.T) { t.Parallel() var source strings.Builder From c2ee9a633534447ca01152f5959d4f79ae474653 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:51:56 -0700 Subject: [PATCH 3/3] Reject renamed inherited member-table keys Inherited members are keyed by their symbol names, not by the keys in base member tables. A selective base lookup must not expose a renamed key that disappears when the inherited properties are enumerated. Check the renamed key before enumeration so lookup and full resolution agree, including when a missing lookup is repeated. --- tsc/internal/checker/checker.go | 3 +++ tsc/internal/checker/checker_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index d3f88b0d6ab36..7f9d4689aa690 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -19674,6 +19674,9 @@ func (c *Checker) resolveObjectTypeMember(resolution *objectMemberResolution, na } } else { property = c.getPropertyOfTypeEx(baseType, name, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) + if property != nil && property.Name() != name { + property = nil + } if property == nil { // Inherited tables use symbol names, which can differ from the base's keys. for _, candidate := range c.getPropertiesOfType(baseType) { diff --git a/tsc/internal/checker/checker_test.go b/tsc/internal/checker/checker_test.go index 0024f269dd338..82ceb56ac4415 100644 --- a/tsc/internal/checker/checker_test.go +++ b/tsc/internal/checker/checker_test.go @@ -242,8 +242,10 @@ instance;`, c, done := program.GetTypeChecker(t.Context()) defer done() typ := c.GetTypeAtLocation(file.Statements.Nodes[3].Expression()) + assert.Assert(t, c.GetPropertyOfType(typ, "different") == nil) value := c.GetPropertyOfType(typ, "value") assert.Assert(t, value != nil) + assert.Assert(t, c.GetPropertyOfType(typ, "different") == nil) properties := c.GetPropertiesOfType(typ) assert.Equal(t, len(properties), 1) assert.Equal(t, properties[0], value)