我能找到的与该主题最接近的记录讨论是微软/TypeScript#16386 https://github.com/microsoft/TypeScript/issues/16386,以及我在 2017 年提交的问题,要求 TypeScript 实现所谓的吸收定律 https://en.wikipedia.org/wiki/Algebra_of_sets#Some_additional_laws_for_unions_and_intersections由此如果T extends U
, then T | U
将持续减少到U
and T & U
将持续减少到T
.
我的问题最终被关闭为同时“固定”和“拒绝” https://github.com/microsoft/TypeScript/issues/16386#issuecomment-380567326。其中一些(但不是全部)行为最终得到了实施。在该问题中从未明确说明为什么没有完全实施交叉点减少,因此以下是我的(希望是受过教育的)猜测。
TL;DR:这种全面的简化要么会破坏人们所依赖的东西,要么就 TS 开发团队的时间或编译器性能而言不值得。
如果 TypeScript 的类型系统完全健全,并且类型简化是设计 TypeScript 的主要考虑因素,那么这些法则可能应该得到实施。但是,尽管类型系统的健全性是 TS 开发的指导原则之一,但它并不是唯一的原则。
TypeScript 的类型系统并不完全健全,而且也并非如此(请参阅TypeScript 设计非目标 #3 https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#non-goals,以及中的讨论微软/TypeScript#9825 https://github.com/microsoft/TypeScript/issues/9825)。因此,一些关于类型的“明显正确”的法则无法在不破坏其他东西的情况下实现。
例如,在声音类型系统中,子类型是传递的,因此T extends U
and U extends V
就意味着T extends V
。但在 TypeScript 中并不总是如此。可选属性的行为方式不健全但有用,所以{a?: string} extends {}
and {} extends {a?: number}
, but not {a?: string} extends {a?: number}
。在声音类型系统中,交集是可交换的,因此T & U
应该是相同的类型U & T
。唉,这在 TypeScript 中又并不总是如此。重载函数 https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads具有多个调用签名是依赖于顺序的,因此{(): string} & {(): number}
是一个不同的类型{(): number} & {(): string}
。对此所做的任何更改都必须仔细考虑,以免破坏大量现实世界的代码。
为了简化而简化也不是 TypeScript 的目标。例如,超额财产检查 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks当人们初始化具有编译器将完全忘记的属性的对象时,会发出警告,因为这通常表明存在编程错误。所以const x: {a: string} = {a: "", b: 0}
产生警告,因为b
财产将不会被追踪。可能是一个错误。另一方面,const x: {a: string} | {a: string, b: number} = {a: "", b: 0}
不会产生警告,因为编译器会期望b
财产可能存在。但{a: string} | {a: string, b: number}
将减少为{a: string}
如果吸收定律在所有地方都得到严格应用。这种简化并不被认为比过多的属性检查更重要。
如果自动应用类型简化规则,您将在类型检查器行为中看到可观察到的重大变化。但即使它没有造成任何破坏,它仍然可能无法实施。据推测,编译器必须花费处理时间来应用建议的缩减规则。也许这些时间可以通过下游性能节省得到回报,但这显然不是真的。有人需要花费时间和精力来实施和测试它。并且添加更多规则会使编译器行为更加复杂。即使是非破坏性的改变也需要付出很大的努力才能被认为是值得的。
无论如何,如果您真的关心简化交叉点,您可以考虑通过辅助函数手动执行此操作。无论您当前在哪里写作X & Y
你可以写MyIntersect<X, Y>
定义为
type MyIntersect<T, U> =
[T] extends [U] ? T :
[U] extends [T] ? U :
T & U;
这在您的示例代码中具有所需的行为:
function andI<T>(x: T): MyIntersect<T, I> { /* ... */ }
function andBase<T>(x: T): MyIntersect<T, Base> { /* ... */ }
let sub = new Sub();
let subAndI = andI(sub); // Has type Sub
let subAndBase = andBase(sub); // Has type Sub
Playground 代码链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBAsiCSA7YEBOBnCBjYAeAKgDRQCqAfFALwBQUdUA2vgLpQQAeKiAJuoyawD8UfFABctegwFtOEHnyZDS4yXVEAyUgG5q1AJbI0AMwCGWaPCgBvNVFQRT3APaIANiCj708MVGCoAK4Q1AC+elhupuh8AELR0Lb0UAD0KVAAEgCCAMIA0n4ZpgBuhgDmUKZQYKj6xaYoUAC2EE0ARmhQxs6oFnyIzk2Gpm7+4OV2NXUN0A5Orh5e6PGYfgHBVP5BELrJwM4AygHlABQAlDZ2yWmkmP4AFtBT9Y3G+hBu3P7OUACOge9gFAAO6mVCIcoAOiu9AcwEC4KgAANvCsIJQACTWYD3byQ1EJUJI3b0cLhaiRaJ8A6BNqyLi8KBorxNMBuVryYB8KxJWGOFzuTzeXxbDaUUUhck3HKBdD7JpjSBQMqBMG8aHGQKIHD6VxLeAndh+LUAawGwMQZz87CWUB5djhCMQiogzmMUBtlC9UAA5M42gArbDAH1QDRaG0AQm9iECblG4d9wtDhg9YY0MLohsqfBO1kYPuTzGNiDNzgtUFCZzODELPh9rC94vWOzCek12uAuudBMwhpLZYtVrT3iZCUuyUdiJthjlpm1rvdaN0UvSSBQGGDIP0OMqPcQxjQDi+oCVYDBphaG-QGq1Or18+48AIZH7ImHcHXaEwOAIxHgFC8nQ+juicwqGtWE7JHy8LTiSdChGwbh3EB0E4qg5ZQIgEDAlAACiqAYag5zwZWbbUB297Oo+aIvm++AfggRibr+RBjpggF2CBUBgcsCQQRcqGThAsHOuwpGIR8KGZg8GG4dhuEEURJF2GSejskC6C0psClQDSbQqRpUBaW0WQ8FY4qPgaJlnNoqTpEUfCntA+nUEZJlmdwzKWTwaInDZdk3I5Lp6bSejhUAA