Skip to content
Merged
256 changes: 191 additions & 65 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.getProperties())
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.getProperties()
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.getProperties()) == 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.getProperties(), func(p *ast.Symbol) bool {
return p.Flags()&ast.SymbolFlagsPrototype == 0
}), len(resolved.properties))
}), len(resolved.getProperties()))
// 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
8 changes: 5 additions & 3 deletions tsc/internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ 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 {
return len(resolved.getSignatures()) == 0 && len(resolved.getIndexInfos()) == 0 && len(c.getLayoutPropertiesOfType(t)) > 0 && core.Every(c.getLayoutPropertiesOfType(t), func(p *ast.Symbol) bool {
return p.Flags()&ast.SymbolFlagsOptional != 0
})
}
Expand Down Expand Up @@ -975,14 +975,16 @@ 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 {
for _, targetProp := range c.getLayoutPropertiesOfType(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.getPropertyOfType(target, targetProp.Name())
}
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
99 changes: 98 additions & 1 deletion tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,82 @@ func (t *StructuredType) ConstructSignatures() []*Signature {
return slices.Clip(t.extra.signatures[t.extra.callSignatureCount:])
}

func (t *StructuredType) getMember(name string) *ast.Symbol {
if t.members != nil {
return t.members[name]
}
lazy := t.getLazyState()
if lazy == nil {
return nil
}
memberInfo := lazy.layout.memberInfoMap[name]
if memberInfo == nil {
return nil
}
return t.getPropertyAt(int(memberInfo.slotIndex))
}

func (t *StructuredType) getMembers() ast.SymbolTable {
if t.members == nil {
if properties := t.getProperties(); len(properties) != 0 {
t.members = make(ast.SymbolTable, len(properties))
for _, member := range properties {
t.members[member.Name()] = member
}
}
}
return t.members
}

func (t *StructuredType) getPropertyAt(index int) *ast.Symbol {
lazy := t.getLazyState()
if lazy == nil {
return t.properties[index]
}
if t.properties == nil {
t.properties = make([]*ast.Symbol, len(lazy.layout.memberInfos))
}
member := t.properties[index]
if member == nil {
memberInfo := lazy.layout.memberInfos[index]
if memberInfo.baseIndex < 0 {
member = t.checker.instantiateSymbol(memberInfo.symbol, lazy.mapper)
} else {
member = t.checker.getPropertyOfType(lazy.baseTypes[memberInfo.baseIndex], memberInfo.symbol.Name())
}
t.properties[index] = member
}
return member
}

func (t *StructuredType) getProperties() []*ast.Symbol {
if lazy := t.getLazyState(); lazy != nil && !lazy.propertiesResolved {
for i := range lazy.layout.memberInfos {
t.getPropertyAt(i)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we paying a little bit of extra overhead for the initial checks of getPropertyAt? Maybe it would make sense to extract the part of getPropertyAt to getPropertyAtWorker that takes the *LazyState, the properties, and current index.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the effect is so minimal it isn't worth it.

}
lazy.propertiesResolved = true
}
return t.AsStructuredType().properties
}

func (t *StructuredType) hasProperties() bool {
if len(t.properties) != 0 {
return true
}
lazy := t.getLazyState()
if lazy == nil {
return false
}
return len(lazy.layout.memberInfos) != 0
}

func (t *StructuredType) getLazyState() *LazyState {
if t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) != 0 {
return t.AsType().AsTypeReference().lazy
}
return nil
}

func (t *StructuredType) getSignatures() []*Signature {
if t.extra == nil {
return nil
Expand All @@ -1165,7 +1241,7 @@ func (t *StructuredType) ensureExtra() *structuredTypeExtra {
}

func (t *StructuredType) Properties() []*ast.Symbol {
return t.properties
return t.getProperties()
}

// Except for tuple type references and reverse mapped types, all object types have an associated symbol.
Expand Down Expand Up @@ -1216,6 +1292,27 @@ type TypeReference struct {
ObjectType
node *ast.Node // TypeReferenceNode | ArrayTypeNode | TupleTypeNode when deferred, else nil
resolvedTypeArguments []*Type
lazy *LazyState // Resolved member layout and instantiation state
}

type LazyState struct {
layout *MemberLayout
mapper *TypeMapper
baseTypes []*Type
propertiesResolved bool
}

type MemberLayout struct {
id uint32 // Unique identifier for the member layout
memberInfoMap map[string]*MemberInfo // MemberInfos keyed by property name
memberInfos []MemberInfo // MemberInfos in sorted order
layoutProperties []*ast.Symbol // Lazily created list of layout properties (symbols corresponding to memberInfos)
}

type MemberInfo struct {
symbol *ast.Symbol // Uninstantiated symbol
baseIndex int32 // Index of base type with symbol, or -1 if locally declared
slotIndex int32 // Index of resolved symbol in members array
}

func (t *TypeReference) AsTypeReference() *TypeReference { return 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))

Loading
Loading