子类型及其超类型的交集是否与子类型相同?

Is intersection of a subtype and its supertype the same as the subtype?

给定以下 TypeScript 代码:

interface A { /* ... */ }
interface B extends A { /* ... */ }

type Mystery = A & B;

Mystery类型的对象应该具有A的所有属性和B的所有属性,因为它是交集类型。 B 类型的对象应该已经具有 A 的所有属性,因为 B extends A.

根据这些定义,类型 Mystery 和类型 B 之间有区别吗?

初步近似,如果B extends A,则A & BB是相同的。编译器甚至会考虑 A & BB 相互赋值:

function foo<A, B extends A>(u: B, m: A & B) {
  u = m; // okay
  m = u; // okay
}

虽然不完全相同:

function foo<A, B extends A>(u: B, m: A & B) {    
  var v: B;
  var v: A & B; // error, not considered identical
}

在实践中,编译器会以不同的方式对待 A & BB。其中一些是被视为错误或设计限制的编译器实现细节;我得去挖掘这些。

但是 A & BB 很容易不同的一个特定地方与调用签名的交集如何被解释为 overloaded 可以以多种方式调用的函数有关,而具有调用签名的接口扩展往往只是 覆盖 父接口调用签名并且只能以一种方式调用。例如:

interface A { method(p: any): void }
interface B extends A { method(): void }

这是允许的,因为 functions of fewer parameters are assignable to functions of more parameters。类型 B 只看到零参数方法,所以你得到以下行为:

declare const a: A;
a.method(0); // okay
declare const b: B;
b.method(); // okay
b.method(0); // error!

由于 method()B 中被重写,因此 b.method() 没有参数是错误的(即使零参数方法可分配给多参数方法,您仍然不能在没有警告的情况下故意调用带有太多参数的函数。

将此与路口进行比较:

type Mystery = A & B;
declare const m: Mystery;
m.method(""); // okay
m.method(); // okay

如果您检查 m.method,您会发现它有两个重载:

// 1/2 method(p: any): void 
// 2/2 method(): void

因此两种方式都可以调用。


Playground link to code