7.0 regression: inherited members are sorted by position, so an empty subinterface changes assignability

未关闭
#65,052 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

@timurzvural 已经在做这个了。

开始于 2026年10月30日。

  • #65065 来自 @timurzvural —— 未关闭
  • #65093 来自 @ahejlsberg —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
25/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
停滞
技术栈
typescript
领域
compilers

调研方向

Start by reviewing getNamedMembers in 7.0.2 or createMemberLayout on main, then compare how inherited members are ordered in the reproduction's deep.ts. Run tsc --noEmit --pretty false deep.ts and check whether wideC produces the same assignability error as wideB; an open linked pull request indicates work is already underway.

由索引模型根据 Issue 内容生成。

描述

🔎 Search Terms

inherited members order, member order, property order, interface extends, empty subinterface, variance, unmeasurable, getNamedMembers, createMemberLayout, missing TS2322, 7.0 regression

🕗 Version & Regression Information
  • This changed between versions 6.0.3 and 7.0.2
  • Still present in 7.1.0-dev.20261030.1.
  • microsoft/typescript-go#1987 (first in @typescript/native-preview 7.0.0-dev.20251031.1) fixed this for one level of inheritance (B below), but not for two (C).
⏯ Playground Link

No response

(Reproduction repo can be found here - it doesn't seem like Playground runs an applicable version for this issue).

💻 Code
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;

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

Run with tsc --noEmit --pretty false deep.ts and no tsconfig, since strict is the default. empty is only there for its message, which lists C's members in the order the checker compares them.

🙁 Actual behavior

With 7.0.2 and 7.1.0-dev.20261030.1, wideC compiles, though C adds nothing to B and the same assignment to B is an error:

deep.ts(1,14): error TS2739: Type '{}' is missing the following properties from type 'C<string>': inherited, own
deep.ts(3,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; }>'.
🙂 Expected behavior

wideC is an error, like wideB, as in 6.0.3:

deep.ts(1,14): error TS2739: Type '{}' is missing the following properties from type 'C<string>': own, inherited
deep.ts(3,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; }>'.
deep.ts(5,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; }>'.
Additional information about the issue

C has no members of its own, so all of its properties are inherited, and they are sorted by declaration position (getNamedMembers in 7.0.2, createMemberLayout on main): inherited, declared in A, comes before own, declared in B. 6.0 takes C's inherited members from B in B's order, which puts B's own members first: own, inherited.

The order changes assignability through variance measurement, which relates C<Sub> to C<Super> and back member by member, stopping at the first member that fails. Required<T> in own marks T unmeasurable (the -?), but only when relating C<Super> to C<Sub>, and that direction fails at inherited first when inherited comes first. T is then measured plain invariant: --generateTrace shows B as [in out (unmeasurable)] and C as [in out]. Invariant, wideC passes because { a: string } and { a: string; b?: number } are each assignable to the other, while wideB gets the structural check that finds own's return types incompatible.

A fix: order inherited members by base type, and within a base type in that base's order, as 6.0 does.

Related, and already fixed on main: in 7.0.2 the order could also depend on other files. An inherited member counted as the interface's own when its text range fell inside the range of one of the interface's declarations in another file, such as a module augmentation, because isDeclarationContainedBy doesn't compare files, so a longer comment in an augmentation could make an assignment compile. #64915 replaced that check for classes and interfaces (first in 7.1.0-dev.20261024.1). I can share a repro if it matters for 7.0.

Found because a library relies on a -? member like own for exact assignments between instantiations of its schema type, and interfaces extending that type lost them on 7.0.

主要语言
Go
星标
112k
派生
15.4k
平均合并
2 天 21 小时
30 天内合并 PR
126

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

microsoft/TypeScript 的其他 Issue

查看 microsoft/TypeScript 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。