Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -19668,24 +19668,37 @@ func (c *Checker) getMemberLayoutKey(t *Type, baseTypes []*Type) CacheHashKey {
}

func (c *Checker) createMemberLayout(t *Type, baseTypes []*Type) *MemberLayout {
// Reuse c.layoutMemberInfos to avoid repeated allocations
layoutMemberInfos := c.layoutMemberInfos
// Reuse c.layoutMemberInfos to avoid repeated allocations. The map records names already
// placed so a later base cannot override an earlier declaration.
seen := c.layoutMemberInfos
c.layoutMemberInfos = nil
memberInfos := make([]MemberInfo, 0)
addMember := func(symbol *ast.Symbol, baseIndex int) {
if _, ok := layoutMemberInfos[symbol.Name()]; !ok {
if layoutMemberInfos == nil {
layoutMemberInfos = make(map[string]MemberInfo)
}
layoutMemberInfos[symbol.Name()] = MemberInfo{symbol: symbol, baseIndex: int32(baseIndex)}
if _, ok := seen[symbol.Name()]; ok {
return
}
if seen == nil {
seen = make(map[string]MemberInfo)
}
info := MemberInfo{symbol: symbol, baseIndex: int32(baseIndex)}
seen[symbol.Name()] = info
memberInfos = append(memberInfos, info)
}
// Add declared members
// Declared members come first. Variance measurement stops at the first failing member,
// and declared members are the ones most likely to carry the structural difference.
for id, symbol := range t.AsInterfaceType().declaredMembers.All() {
if c.isNamedMember(symbol, id) {
addMember(symbol, -1)
}
}
// Add inherited members
declaredCount := len(memberInfos)
slices.SortFunc(memberInfos[:declaredCount], func(a, b MemberInfo) int {
return c.compareSymbols(a.symbol, b.symbol)
})
// Inherited members follow in base-clause order, and within a base in that base's order.
// Sorting them by declaration position pulls a grandbase member ahead of the base's own
// members, so an empty subtype is measured differently from its base.
// See https://github.com/microsoft/TypeScript/issues/65052.
for baseIndex, baseType := range baseTypes {
if baseType.objectFlags&(ObjectFlagsReference|ObjectFlagsClassOrInterface) != 0 {
c.resolveStructuredTypeMembers(baseType)
Expand All @@ -19702,20 +19715,9 @@ func (c *Checker) createMemberLayout(t *Type, baseTypes []*Type) *MemberLayout {
}
}
}
memberInfos := slices.AppendSeq(make([]MemberInfo, 0, len(layoutMemberInfos)), maps.Values(layoutMemberInfos))
clear(layoutMemberInfos)
c.layoutMemberInfos = layoutMemberInfos
// Sort by symbols, but put declared members (baseIndex < 0) first
slices.SortFunc(memberInfos, func(a, b MemberInfo) int {
if a.baseIndex < 0 && b.baseIndex >= 0 {
return -1
}
if a.baseIndex >= 0 && b.baseIndex < 0 {
return 1
}
return c.CompareSymbols(a.symbol, b.symbol)
})
// Store slot indices in the sorted memberInfos and build the memberInfoMap. Slot indices are used to
clear(seen)
c.layoutMemberInfos = seen
// Store slot indices in memberInfos and build the memberInfoMap. Slot indices are used to
// identify slots in the instantiated type's properties list.
memberInfoMap := make(map[string]*MemberInfo, len(memberInfos))
for i := range memberInfos {
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,7 @@ type LazyState struct {
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
memberInfos []MemberInfo // Declared members, then inherited members in base order
layoutProperties []*ast.Symbol // Lazily created list of layout properties (symbols corresponding to memberInfos)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface I6 extends C4 {}
class C5 implements I6 {}`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
// Implemented members follow I6's base order: C4's fields, then C3's, then C2's.
f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
Description: "Implement interface 'I6'",
NewFileContent: `// Referenced throughout the inheritance chain.
Expand All @@ -64,12 +65,12 @@ class C4 extends C3 implements I0, I4, I5 {

interface I6 extends C4 {}
class C5 implements I6 {
c: number;
e: number;
f: number;
a: number;
b: number;
d: number;
e: number;
f: number;
c: number;
}`,
Index: 0,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
emptySubtypeInheritedMemberOrder.ts(5,14): error TS2739: Type '{}' is missing the following properties from type 'C<string>': own, inherited
emptySubtypeInheritedMemberOrder.ts(8,14): error TS2322: Type 'B<{ a: string; }>' is not assignable to type 'B<{ a: string; b?: number | undefined; }>'.
The types returned by 'own(...)' are incompatible between these types.
Property 'b' is missing in type 'Required<{ a: string; }>' but required in type 'Required<{ a: string; b?: number | undefined; }>'.
emptySubtypeInheritedMemberOrder.ts(11,14): error TS2322: Type 'C<{ a: string; }>' is not assignable to type 'C<{ a: string; b?: number | undefined; }>'.
The types returned by 'own(...)' are incompatible between these types.
Property 'b' is missing in type 'Required<{ a: string; }>' but required in type 'Required<{ a: string; b?: number | undefined; }>'.
emptySubtypeInheritedMemberOrder.ts(14,14): error TS2322: Type 'D<{ a: string; }>' is not assignable to type 'D<{ a: string; b?: number | undefined; }>'.
The types returned by 'own(...)' are incompatible between these types.
Property 'b' is missing in type 'Required<{ a: string; }>' but required in type 'Required<{ a: string; b?: number | undefined; }>'.
emptySubtypeInheritedMemberOrder.ts(16,14): error TS2739: Type '{}' is missing the following properties from type 'Both': right, left


==== emptySubtypeInheritedMemberOrder.ts (5 errors) ====
// Inherited members keep the base type's order. Sorting them by declaration position
// makes an empty subtype invariant and drops the assignability error.
// https://github.com/microsoft/TypeScript/issues/65052

export const empty: C<string> = {};
~~~~~
!!! error TS2739: Type '{}' is missing the following properties from type 'C<string>': own, inherited

declare const narrowB: B<{ a: string }>;
export const wideB: B<{ a: string; b?: number }> = narrowB;
~~~~~
!!! error TS2322: Type 'B<{ a: string; }>' is not assignable to type 'B<{ a: string; b?: number | undefined; }>'.
!!! error TS2322: The types returned by 'own(...)' are incompatible between these types.
!!! error TS2322: Property 'b' is missing in type 'Required<{ a: string; }>' but required in type 'Required<{ a: string; b?: number | undefined; }>'.
!!! related TS2728 emptySubtypeInheritedMemberOrder.ts:8:36: 'b' is declared here.

declare const narrowC: C<{ a: string }>;
export const wideC: C<{ a: string; b?: number }> = narrowC;
~~~~~
!!! error TS2322: Type 'C<{ a: string; }>' is not assignable to type 'C<{ a: string; b?: number | undefined; }>'.
!!! error TS2322: The types returned by 'own(...)' are incompatible between these types.
!!! error TS2322: Property 'b' is missing in type 'Required<{ a: string; }>' but required in type 'Required<{ a: string; b?: number | undefined; }>'.
!!! related TS2728 emptySubtypeInheritedMemberOrder.ts:11:36: 'b' is declared here.

declare const narrowD: D<{ a: string }>;
export const wideD: D<{ a: string; b?: number }> = narrowD;
~~~~~
!!! error TS2322: Type 'D<{ a: string; }>' is not assignable to type 'D<{ a: string; b?: number | undefined; }>'.
!!! error TS2322: The types returned by 'own(...)' are incompatible between these types.
!!! error TS2322: Property 'b' is missing in type 'Required<{ a: string; }>' but required in type 'Required<{ a: string; b?: number | undefined; }>'.
!!! related TS2728 emptySubtypeInheritedMemberOrder.ts:14:36: 'b' is declared here.

export const both: Both = {};
~~~~
!!! error TS2739: Type '{}' is missing the following properties from type 'Both': right, left

interface A<T> {
inherited: () => T;
}

interface B<T> extends A<T> {
own: (x: T) => Required<T>;
}

interface C<T> extends B<T> {}

interface D<T> extends C<T> {}

interface Left {
left: string;
}

interface Right {
right: string;
}

interface Both extends Right, Left {}

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

=== emptySubtypeInheritedMemberOrder.ts ===
// Inherited members keep the base type's order. Sorting them by declaration position
// makes an empty subtype invariant and drops the assignability error.
// https://github.com/microsoft/TypeScript/issues/65052

export const empty: C<string> = {};
>empty : Symbol(empty, Decl(emptySubtypeInheritedMemberOrder.ts, 4, 12))
>C : Symbol(C, Decl(emptySubtypeInheritedMemberOrder.ts, 23, 1))

declare const narrowB: B<{ a: string }>;
>narrowB : Symbol(narrowB, Decl(emptySubtypeInheritedMemberOrder.ts, 6, 13))
>B : Symbol(B, Decl(emptySubtypeInheritedMemberOrder.ts, 19, 1))
>a : Symbol(a, Decl(emptySubtypeInheritedMemberOrder.ts, 6, 26))

export const wideB: B<{ a: string; b?: number }> = narrowB;
>wideB : Symbol(wideB, Decl(emptySubtypeInheritedMemberOrder.ts, 7, 12))
>B : Symbol(B, Decl(emptySubtypeInheritedMemberOrder.ts, 19, 1))
>a : Symbol(a, Decl(emptySubtypeInheritedMemberOrder.ts, 7, 23))
>b : Symbol(b, Decl(emptySubtypeInheritedMemberOrder.ts, 7, 34))
>narrowB : Symbol(narrowB, Decl(emptySubtypeInheritedMemberOrder.ts, 6, 13))

declare const narrowC: C<{ a: string }>;
>narrowC : Symbol(narrowC, Decl(emptySubtypeInheritedMemberOrder.ts, 9, 13))
>C : Symbol(C, Decl(emptySubtypeInheritedMemberOrder.ts, 23, 1))
>a : Symbol(a, Decl(emptySubtypeInheritedMemberOrder.ts, 9, 26))

export const wideC: C<{ a: string; b?: number }> = narrowC;
>wideC : Symbol(wideC, Decl(emptySubtypeInheritedMemberOrder.ts, 10, 12))
>C : Symbol(C, Decl(emptySubtypeInheritedMemberOrder.ts, 23, 1))
>a : Symbol(a, Decl(emptySubtypeInheritedMemberOrder.ts, 10, 23))
>b : Symbol(b, Decl(emptySubtypeInheritedMemberOrder.ts, 10, 34))
>narrowC : Symbol(narrowC, Decl(emptySubtypeInheritedMemberOrder.ts, 9, 13))

declare const narrowD: D<{ a: string }>;
>narrowD : Symbol(narrowD, Decl(emptySubtypeInheritedMemberOrder.ts, 12, 13))
>D : Symbol(D, Decl(emptySubtypeInheritedMemberOrder.ts, 25, 30))
>a : Symbol(a, Decl(emptySubtypeInheritedMemberOrder.ts, 12, 26))

export const wideD: D<{ a: string; b?: number }> = narrowD;
>wideD : Symbol(wideD, Decl(emptySubtypeInheritedMemberOrder.ts, 13, 12))
>D : Symbol(D, Decl(emptySubtypeInheritedMemberOrder.ts, 25, 30))
>a : Symbol(a, Decl(emptySubtypeInheritedMemberOrder.ts, 13, 23))
>b : Symbol(b, Decl(emptySubtypeInheritedMemberOrder.ts, 13, 34))
>narrowD : Symbol(narrowD, Decl(emptySubtypeInheritedMemberOrder.ts, 12, 13))

export const both: Both = {};
>both : Symbol(both, Decl(emptySubtypeInheritedMemberOrder.ts, 15, 12))
>Both : Symbol(Both, Decl(emptySubtypeInheritedMemberOrder.ts, 35, 1))

interface A<T> {
>A : Symbol(A, Decl(emptySubtypeInheritedMemberOrder.ts, 15, 29))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 17, 12))

inherited: () => T;
>inherited : Symbol(A.inherited, Decl(emptySubtypeInheritedMemberOrder.ts, 17, 16))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 17, 12))
}

interface B<T> extends A<T> {
>B : Symbol(B, Decl(emptySubtypeInheritedMemberOrder.ts, 19, 1))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 21, 12))
>A : Symbol(A, Decl(emptySubtypeInheritedMemberOrder.ts, 15, 29))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 21, 12))

own: (x: T) => Required<T>;
>own : Symbol(B.own, Decl(emptySubtypeInheritedMemberOrder.ts, 21, 29))
>x : Symbol(x, Decl(emptySubtypeInheritedMemberOrder.ts, 22, 10))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 21, 12))
>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 21, 12))
}

interface C<T> extends B<T> {}
>C : Symbol(C, Decl(emptySubtypeInheritedMemberOrder.ts, 23, 1))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 25, 12))
>B : Symbol(B, Decl(emptySubtypeInheritedMemberOrder.ts, 19, 1))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 25, 12))

interface D<T> extends C<T> {}
>D : Symbol(D, Decl(emptySubtypeInheritedMemberOrder.ts, 25, 30))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 27, 12))
>C : Symbol(C, Decl(emptySubtypeInheritedMemberOrder.ts, 23, 1))
>T : Symbol(T, Decl(emptySubtypeInheritedMemberOrder.ts, 27, 12))

interface Left {
>Left : Symbol(Left, Decl(emptySubtypeInheritedMemberOrder.ts, 27, 30))

left: string;
>left : Symbol(Left.left, Decl(emptySubtypeInheritedMemberOrder.ts, 29, 16))
}

interface Right {
>Right : Symbol(Right, Decl(emptySubtypeInheritedMemberOrder.ts, 31, 1))

right: string;
>right : Symbol(Right.right, Decl(emptySubtypeInheritedMemberOrder.ts, 33, 17))
}

interface Both extends Right, Left {}
>Both : Symbol(Both, Decl(emptySubtypeInheritedMemberOrder.ts, 35, 1))
>Right : Symbol(Right, Decl(emptySubtypeInheritedMemberOrder.ts, 31, 1))
>Left : Symbol(Left, Decl(emptySubtypeInheritedMemberOrder.ts, 27, 30))

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

=== emptySubtypeInheritedMemberOrder.ts ===
// Inherited members keep the base type's order. Sorting them by declaration position
// makes an empty subtype invariant and drops the assignability error.
// https://github.com/microsoft/TypeScript/issues/65052

export const empty: C<string> = {};
>empty : C<string>
>{} : {}

declare const narrowB: B<{ a: string }>;
>narrowB : B<{ a: string; }>
>a : string

export const wideB: B<{ a: string; b?: number }> = narrowB;
>wideB : B<{ a: string; b?: number; }>
>a : string
>b : number | undefined
>narrowB : B<{ a: string; }>

declare const narrowC: C<{ a: string }>;
>narrowC : C<{ a: string; }>
>a : string

export const wideC: C<{ a: string; b?: number }> = narrowC;
>wideC : C<{ a: string; b?: number; }>
>a : string
>b : number | undefined
>narrowC : C<{ a: string; }>

declare const narrowD: D<{ a: string }>;
>narrowD : D<{ a: string; }>
>a : string

export const wideD: D<{ a: string; b?: number }> = narrowD;
>wideD : D<{ a: string; b?: number; }>
>a : string
>b : number | undefined
>narrowD : D<{ a: string; }>

export const both: Both = {};
>both : Both
>{} : {}

interface A<T> {
inherited: () => T;
>inherited : () => T
}

interface B<T> extends A<T> {
own: (x: T) => Required<T>;
>own : (x: T) => Required<T>
>x : T
}

interface C<T> extends B<T> {}

interface D<T> extends C<T> {}

interface Left {
left: string;
>left : string
}

interface Right {
right: string;
>right : string
}

interface Both extends Right, Left {}

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @strict: true
// @noEmit: true

// Inherited members keep the base type's order. Sorting them by declaration position
// makes an empty subtype invariant and drops the assignability error.
// https://github.com/microsoft/TypeScript/issues/65052

export const empty: C<string> = {};

declare const narrowB: B<{ a: string }>;
export const wideB: B<{ a: string; b?: number }> = narrowB;

declare const narrowC: C<{ a: string }>;
export const wideC: C<{ a: string; b?: number }> = narrowC;

declare const narrowD: D<{ a: string }>;
export const wideD: D<{ a: string; b?: number }> = narrowD;

export const both: Both = {};

interface A<T> {
inherited: () => T;
}

interface B<T> extends A<T> {
own: (x: T) => Required<T>;
}

interface C<T> extends B<T> {}

interface D<T> extends C<T> {}

interface Left {
left: string;
}

interface Right {
right: string;
}

interface Both extends Right, Left {}