Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ad18168
Resolve class and interface members through shared layouts
jakebailey Oct 13, 2026
c7b046b
Rebuild member layouts when a base instantiation fails
jakebailey Oct 13, 2026
ecc74a8
Reuse scratch storage when building member layouts
jakebailey Oct 13, 2026
095d945
Make member layouts easier to follow
jakebailey Oct 13, 2026
3b7f539
Merge remote-tracking branch 'upstream/main' into shared-member-layouts
jakebailey Oct 14, 2026
a87c1dc
Account for lazy members in the structured type size check
jakebailey Oct 14, 2026
281ff13
Add test for namespace imports of callable export= values
jakebailey Oct 14, 2026
32efca3
Materialize lazy members when cloning a module type
jakebailey Oct 14, 2026
ff5b586
Test inherited member lookups before enumeration
jakebailey Oct 14, 2026
f4c121e
Test member layouts after a base hits the instantiation limit
jakebailey Oct 14, 2026
0b2c03d
Rebuild member layouts when any base layout isn't shared
jakebailey Oct 14, 2026
524eedc
Test member layouts of interfaces extending variadic tuples
jakebailey Oct 14, 2026
1988b23
Reuse member layouts only with the same base layouts
jakebailey Oct 14, 2026
c82b73e
Test member layouts of bases with this-dependent intersections
jakebailey Oct 14, 2026
2ffc24b
Test member layouts of types with self-referential late-bound names
jakebailey Oct 14, 2026
f4227dc
Share member layouts only when every base has a layout
jakebailey Oct 14, 2026
81bfcec
Drop member layouts built from partial declared members
jakebailey Oct 14, 2026
cfab455
Verify member layouts against eager member resolution
jakebailey Oct 14, 2026
1071609
Revert "Verify member layouts against eager member resolution"
jakebailey Oct 15, 2026
a529cc2
Remove redundant state from lazy members
jakebailey Oct 15, 2026
1e71e18
Find layout members by their symbols' names
jakebailey Oct 15, 2026
2d4a806
Share member layouts between instantiations with the same bases
jakebailey Oct 15, 2026
b8f7c08
Find inherited members through the bases a layout was chosen for
jakebailey Oct 15, 2026
a0d94c2
Order layout members with getNamedMembers
jakebailey Oct 15, 2026
13405d5
Reapply "Verify member layouts against eager member resolution"
jakebailey Oct 15, 2026
14a8789
Revert "Reapply "Verify member layouts against eager member resolution""
jakebailey Oct 15, 2026
46b83d7
Remove the instantiation limit member layout test
jakebailey Oct 15, 2026
ec37098
Remove the test that used the instantiation limit hooks
jakebailey Oct 15, 2026
2c9f837
Rename property shapes to uninstantiated properties
jakebailey Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 318 additions & 46 deletions tsc/internal/checker/checker.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tsc/internal/checker/nodebuilder_hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (b *NodeBuilderImpl) expandInterfaceDecl(symbol *ast.Symbol) *ast.Node {
members = append(members, b.signatureToSignatureDeclarationHelper(sig, ast.KindCallSignature, nil))
}
// Properties, filtering inherited
filteredProps := b.filterInheritedProperties(interfaceType, baseTypes, resolved.properties)
filteredProps := b.filterInheritedProperties(interfaceType, baseTypes, resolved.Properties())
members = b.serializePropertiesWithTruncation(filteredProps, members)

// Heritage clauses
Expand Down
10 changes: 5 additions & 5 deletions tsc/internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ func (b *NodeBuilderImpl) getResolvedTypeWithoutAbstractConstructSignatures(t *S
t.extra.objectTypeWithoutAbstractConstructSignatures = t.AsType()
return t.AsType()
}
typeCopy := b.ch.newAnonymousType(t.symbol, t.members, t.CallSignatures(), core.IfElse(len(constructSignatures) > 0, constructSignatures, []*Signature{}), t.getIndexInfos())
typeCopy := b.ch.newAnonymousType(t.symbol, t.getMembers(), t.CallSignatures(), core.IfElse(len(constructSignatures) > 0, constructSignatures, []*Signature{}), t.getIndexInfos())
t.extra.objectTypeWithoutAbstractConstructSignatures = typeCopy
typeCopy.AsStructuredType().ensureExtra().objectTypeWithoutAbstractConstructSignatures = typeCopy
return typeCopy
Expand Down Expand Up @@ -2758,7 +2758,7 @@ func (b *NodeBuilderImpl) createTypeNodesFromResolvedType(resolvedType *Structur
typeElements = slices.Concat(typeElements, b.indexInfoToObjectComputedNamesOrSignatureDeclaration(info, typeNode))
}

properties := resolvedType.properties
properties := resolvedType.Properties()
if len(properties) == 0 {
return b.f.NewNodeList(typeElements)
}
Expand Down Expand Up @@ -2807,7 +2807,7 @@ func (b *NodeBuilderImpl) createTypeNodeFromObjectType(t *Type) *ast.TypeNode {
resolved := b.ch.resolveStructuredTypeMembers(t)
callSigs := resolved.CallSignatures()
ctorSigs := resolved.ConstructSignatures()
if len(resolved.properties) == 0 && len(resolved.getIndexInfos()) == 0 {
if len(resolved.Properties()) == 0 && len(resolved.getIndexInfos()) == 0 {
if len(callSigs) == 0 && len(ctorSigs) == 0 {
b.ctx.approximateLength += 2
result := b.f.NewTypeLiteralNode(b.f.NewNodeList([]*ast.Node{}))
Expand Down Expand Up @@ -2836,9 +2836,9 @@ func (b *NodeBuilderImpl) createTypeNodeFromObjectType(t *Type) *ast.TypeNode {
return b.ch.getOrCreateTypeFromSignature(s)
})
// count the number of type elements excluding abstract constructors
typeElementCount := len(callSigs) + (len(ctorSigs) - len(abstractSignatures)) + len(resolved.getIndexInfos()) + core.IfElse(b.ctx.flags&nodebuilder.FlagsWriteClassExpressionAsTypeLiteral != 0, core.CountWhere(resolved.properties, func(p *ast.Symbol) bool {
typeElementCount := len(callSigs) + (len(ctorSigs) - len(abstractSignatures)) + len(resolved.getIndexInfos()) + core.IfElse(b.ctx.flags&nodebuilder.FlagsWriteClassExpressionAsTypeLiteral != 0, core.CountWhere(resolved.Properties(), func(p *ast.Symbol) bool {
return p.Flags()&ast.SymbolFlagsPrototype == 0
}), len(resolved.properties))
}), len(resolved.Properties()))
// don't include an empty object literal if there were no other static-side
// properties to write, i.e. `abstract class C { }` becomes `abstract new () => {}`
// and not `(abstract new () => {}) & {}`
Expand Down
12 changes: 9 additions & 3 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,8 @@ func (c *Checker) elaborateArrowFunction(node *ast.Node, source *Type, target *T
func (c *Checker) isWeakType(t *Type) bool {
if t.flags&TypeFlagsObject != 0 {
resolved := c.resolveStructuredTypeMembers(t)
return len(resolved.getSignatures()) == 0 && len(resolved.getIndexInfos()) == 0 && len(resolved.properties) > 0 && core.Every(resolved.properties, func(p *ast.Symbol) bool {
properties := resolved.uninstantiatedProperties()
return len(resolved.getSignatures()) == 0 && len(resolved.getIndexInfos()) == 0 && len(properties) > 0 && core.Every(properties, func(p *ast.Symbol) bool {
return p.Flags()&ast.SymbolFlagsOptional != 0
})
}
Expand Down Expand Up @@ -975,14 +976,19 @@ func (c *Checker) getUnmatchedProperties(source *Type, target *Type, requireOpti
}

func (c *Checker) getUnmatchedPropertiesWorker(source *Type, target *Type, requireOptionalProperties bool, matchDiscriminantProperties bool, propsOut *[]*ast.Symbol) *ast.Symbol {
properties := c.getPropertiesOfType(target)
for _, targetProp := range properties {
// Names and flags of properties are known without resolving them, so only resolve the ones we report or compare.
// Reduce the target once, since getting its apparent type can allocate.
target = c.getReducedApparentType(target)
for i, targetProp := range c.getUninstantiatedPropertiesOfType(target) {
// TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass
if isStaticPrivateIdentifierProperty(targetProp) {
continue
}
if requireOptionalProperties || targetProp.Flags()&ast.SymbolFlagsOptional == 0 && targetProp.CheckFlags()&ast.CheckFlagsPartial == 0 {
sourceProp := c.getPropertyOfType(source, targetProp.Name())
if sourceProp == nil || matchDiscriminantProperties {
targetProp = c.getPropertyOfTypeAt(target, i)
}
if sourceProp == nil {
if propsOut == nil {
return targetProp
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (c *Checker) ForEachExportAndPropertyOfModule(moduleSymbol *ast.Symbol, cb
if reducedType.flags&TypeFlagsStructuredType == 0 {
return
}
for name, symbol := range c.resolveStructuredTypeMembers(reducedType).members {
for name, symbol := range c.resolveStructuredTypeMembers(reducedType).getMembers() {
if c.isNamedMember(symbol, name) {
cb(symbol, name)
}
Expand Down
35 changes: 35 additions & 0 deletions tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,7 @@ type StructuredType struct {
ConstrainedType
members ast.SymbolTable
properties []*ast.Symbol
lazy *lazyMembers // Members resolved on demand, in which case members and properties are nil until needed
extra *structuredTypeExtra
}

Expand Down Expand Up @@ -1165,9 +1166,42 @@ func (t *StructuredType) ensureExtra() *structuredTypeExtra {
}

func (t *StructuredType) Properties() []*ast.Symbol {
if t.properties == nil && t.lazy != nil {
t.properties = t.checker.getLazyProperties(t.lazy)
}
return t.properties
}

// Like Properties, but without resolving lazy members, so the properties may be the symbols they would be
// instantiated from. These have the same names and flags as the actual properties, but not the same types.
func (t *StructuredType) uninstantiatedProperties() []*ast.Symbol {
if t.properties == nil && t.lazy != nil {
return t.lazy.layout.properties()
}
return t.properties
}

func (t *StructuredType) property(i int) *ast.Symbol {
if t.properties == nil && t.lazy != nil {
return t.checker.getLazyMember(t.lazy, i)
}
return t.properties[i]
}

func (t *StructuredType) getMember(name string) *ast.Symbol {
if t.lazy != nil {
return t.checker.getLazyMemberByName(t.lazy, name)
}
return t.members[name]
}

func (t *StructuredType) getMembers() ast.SymbolTable {
if t.members == nil && t.lazy != nil {
t.members = t.checker.getLazyMemberTable(t.lazy)
}
return t.members
}

// Except for tuple type references and reverse mapped types, all object types have an associated symbol.
// Possible object type instances are listed in the following.

Expand Down Expand Up @@ -1235,6 +1269,7 @@ type InterfaceType struct {
declaredCallSignatures []*Signature // Declared call signatures
declaredConstructSignatures []*Signature // Declared construct signatures
declaredIndexInfos []*IndexInfo // Declared index signatures
memberLayout *memberLayout // Member layout shared by all instantiations, if possible
}

func (t *InterfaceType) AsInterfaceType() *InterfaceType { return t }
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ func TestCompactValueSymbolLinksLayout(t *testing.T) {

func TestCompactStructuredTypeLayout(t *testing.T) {
t.Parallel()
assert.Equal(t, unsafe.Sizeof(StructuredType{}), unsafe.Sizeof(ConstrainedType{})+5*unsafe.Sizeof(uintptr(0)))
assert.Equal(t, unsafe.Sizeof(StructuredType{}), unsafe.Sizeof(ConstrainedType{})+6*unsafe.Sizeof(uintptr(0)))
}

func TestStructuredTypeExtra(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//// [tests/cases/compiler/lateBoundMemberLayoutReentrancy.ts] ////

//// [lateBoundMemberLayoutReentrancy.ts]
declare const rI: RI<"a">;
rI.x
interface RI<T extends "a" | "b"> {
x: T;
[rI.x]: "b";
}
declare const rI2: RI<"b">;
export const v1 = rI2.a;
export const v2 = rI.a;
export type K2 = keyof RI<"b">;
export const k: K2 = "a";
export const o: RI<"b"> = { x: "b", a: "b" };

declare const rC: RC<"a">;
rC.x
declare class RC<T extends "a" | "b"> {
x: T;
[rC.x]: "b";
}
declare const rC2: RC<"b">;
export const w1 = rC2.a;
export const w2 = rC.a;


//// [lateBoundMemberLayoutReentrancy.js]
rI.x;
export const v1 = rI2.a;
export const v2 = rI.a;
export const k = "a";
export const o = { x: "b", a: "b" };
rC.x;
export const w1 = rC2.a;
export const w2 = rC.a;


//// [lateBoundMemberLayoutReentrancy.d.ts]
declare const rI: RI<"a">;
interface RI<T extends "a" | "b"> {
x: T;
[rI.x]: "b";
}
export declare const v1: "b";
export declare const v2: "b";
export type K2 = keyof RI<"b">;
export declare const k: K2;
export declare const o: RI<"b">;
export declare const w1: "b";
export declare const w2: "b";
export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//// [tests/cases/compiler/lateBoundMemberLayoutReentrancy.ts] ////

=== lateBoundMemberLayoutReentrancy.ts ===
declare const rI: RI<"a">;
>rI : Symbol(rI, Decl(lateBoundMemberLayoutReentrancy.ts, 0, 13))
>RI : Symbol(RI, Decl(lateBoundMemberLayoutReentrancy.ts, 1, 4))

rI.x
>rI.x : Symbol(RI.x, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 35))
>rI : Symbol(rI, Decl(lateBoundMemberLayoutReentrancy.ts, 0, 13))
>x : Symbol(RI.x, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 35))

interface RI<T extends "a" | "b"> {
>RI : Symbol(RI, Decl(lateBoundMemberLayoutReentrancy.ts, 1, 4))
>T : Symbol(T, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 13))

x: T;
>x : Symbol(RI.x, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 35))
>T : Symbol(T, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 13))

[rI.x]: "b";
>[rI.x] : Symbol(RI[rI.x], Decl(lateBoundMemberLayoutReentrancy.ts, 3, 9))
>rI.x : Symbol(RI.x, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 35))
>rI : Symbol(rI, Decl(lateBoundMemberLayoutReentrancy.ts, 0, 13))
>x : Symbol(RI.x, Decl(lateBoundMemberLayoutReentrancy.ts, 2, 35))
}
declare const rI2: RI<"b">;
>rI2 : Symbol(rI2, Decl(lateBoundMemberLayoutReentrancy.ts, 6, 13))
>RI : Symbol(RI, Decl(lateBoundMemberLayoutReentrancy.ts, 1, 4))

export const v1 = rI2.a;
>v1 : Symbol(v1, Decl(lateBoundMemberLayoutReentrancy.ts, 7, 12))
>rI2.a : Symbol(RI[rI.x], Decl(lateBoundMemberLayoutReentrancy.ts, 3, 9))
>rI2 : Symbol(rI2, Decl(lateBoundMemberLayoutReentrancy.ts, 6, 13))
>a : Symbol(RI[rI.x], Decl(lateBoundMemberLayoutReentrancy.ts, 3, 9))

export const v2 = rI.a;
>v2 : Symbol(v2, Decl(lateBoundMemberLayoutReentrancy.ts, 8, 12))
>rI.a : Symbol(RI[rI.x], Decl(lateBoundMemberLayoutReentrancy.ts, 3, 9))
>rI : Symbol(rI, Decl(lateBoundMemberLayoutReentrancy.ts, 0, 13))
>a : Symbol(RI[rI.x], Decl(lateBoundMemberLayoutReentrancy.ts, 3, 9))

export type K2 = keyof RI<"b">;
>K2 : Symbol(K2, Decl(lateBoundMemberLayoutReentrancy.ts, 8, 23))
>RI : Symbol(RI, Decl(lateBoundMemberLayoutReentrancy.ts, 1, 4))

export const k: K2 = "a";
>k : Symbol(k, Decl(lateBoundMemberLayoutReentrancy.ts, 10, 12))
>K2 : Symbol(K2, Decl(lateBoundMemberLayoutReentrancy.ts, 8, 23))

export const o: RI<"b"> = { x: "b", a: "b" };
>o : Symbol(o, Decl(lateBoundMemberLayoutReentrancy.ts, 11, 12))
>RI : Symbol(RI, Decl(lateBoundMemberLayoutReentrancy.ts, 1, 4))
>x : Symbol(x, Decl(lateBoundMemberLayoutReentrancy.ts, 11, 27))
>a : Symbol(a, Decl(lateBoundMemberLayoutReentrancy.ts, 11, 35))

declare const rC: RC<"a">;
>rC : Symbol(rC, Decl(lateBoundMemberLayoutReentrancy.ts, 13, 13))
>RC : Symbol(RC, Decl(lateBoundMemberLayoutReentrancy.ts, 14, 4))

rC.x
>rC.x : Symbol(RC.x, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 39))
>rC : Symbol(rC, Decl(lateBoundMemberLayoutReentrancy.ts, 13, 13))
>x : Symbol(RC.x, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 39))

declare class RC<T extends "a" | "b"> {
>RC : Symbol(RC, Decl(lateBoundMemberLayoutReentrancy.ts, 14, 4))
>T : Symbol(T, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 17))

x: T;
>x : Symbol(RC.x, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 39))
>T : Symbol(T, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 17))

[rC.x]: "b";
>[rC.x] : Symbol(RC[rC.x], Decl(lateBoundMemberLayoutReentrancy.ts, 16, 9))
>rC.x : Symbol(RC.x, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 39))
>rC : Symbol(rC, Decl(lateBoundMemberLayoutReentrancy.ts, 13, 13))
>x : Symbol(RC.x, Decl(lateBoundMemberLayoutReentrancy.ts, 15, 39))
}
declare const rC2: RC<"b">;
>rC2 : Symbol(rC2, Decl(lateBoundMemberLayoutReentrancy.ts, 19, 13))
>RC : Symbol(RC, Decl(lateBoundMemberLayoutReentrancy.ts, 14, 4))

export const w1 = rC2.a;
>w1 : Symbol(w1, Decl(lateBoundMemberLayoutReentrancy.ts, 20, 12))
>rC2.a : Symbol(RC[rC.x], Decl(lateBoundMemberLayoutReentrancy.ts, 16, 9))
>rC2 : Symbol(rC2, Decl(lateBoundMemberLayoutReentrancy.ts, 19, 13))
>a : Symbol(RC[rC.x], Decl(lateBoundMemberLayoutReentrancy.ts, 16, 9))

export const w2 = rC.a;
>w2 : Symbol(w2, Decl(lateBoundMemberLayoutReentrancy.ts, 21, 12))
>rC.a : Symbol(RC[rC.x], Decl(lateBoundMemberLayoutReentrancy.ts, 16, 9))
>rC : Symbol(rC, Decl(lateBoundMemberLayoutReentrancy.ts, 13, 13))
>a : Symbol(RC[rC.x], Decl(lateBoundMemberLayoutReentrancy.ts, 16, 9))

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//// [tests/cases/compiler/lateBoundMemberLayoutReentrancy.ts] ////

=== lateBoundMemberLayoutReentrancy.ts ===
declare const rI: RI<"a">;
>rI : RI<"a">

rI.x
>rI.x : "a"
>rI : RI<"a">
>x : "a"

interface RI<T extends "a" | "b"> {
x: T;
>x : T

[rI.x]: "b";
>[rI.x] : "b"
>rI.x : "a"
>rI : RI<"a">
>x : "a"
}
declare const rI2: RI<"b">;
>rI2 : RI<"b">

export const v1 = rI2.a;
>v1 : "b"
>rI2.a : "b"
>rI2 : RI<"b">
>a : "b"

export const v2 = rI.a;
>v2 : "b"
>rI.a : "b"
>rI : RI<"a">
>a : "b"

export type K2 = keyof RI<"b">;
>K2 : keyof RI<"b">

export const k: K2 = "a";
>k : keyof RI<"b">
>"a" : "a"

export const o: RI<"b"> = { x: "b", a: "b" };
>o : RI<"b">
>{ x: "b", a: "b" } : { x: "b"; a: "b"; }
>x : "b"
>"b" : "b"
>a : "b"
>"b" : "b"

declare const rC: RC<"a">;
>rC : RC<"a">

rC.x
>rC.x : "a"
>rC : RC<"a">
>x : "a"

declare class RC<T extends "a" | "b"> {
>RC : RC<T>

x: T;
>x : T

[rC.x]: "b";
>[rC.x] : "b"
>rC.x : "a"
>rC : RC<"a">
>x : "a"
}
declare const rC2: RC<"b">;
>rC2 : RC<"b">

export const w1 = rC2.a;
>w1 : "b"
>rC2.a : "b"
>rC2 : RC<"b">
>a : "b"

export const w2 = rC.a;
>w2 : "b"
>rC.a : "b"
>rC : RC<"a">
>a : "b"

Loading
Loading