[英]Typescript intersection type cast to one of the types that makes the intersection
[英]Why does TypeScript not simplify the intersection of a type and one of its super-types?
有沒有辦法讓 TypeScript 的檢查器簡化交叉類型的不必要元素,或者我錯了它們是不必要的?
IIUC,類型SubType & SuperType
等同於SubType
,但打字稿似乎沒有執行這種簡化。
如下所示,我將class Sub
定義為class Base
和interface I
的聲明子類型。
然后,我為每個采用<T>
的超類型定義通用函數,如果它通過了該超類型的保護,則返回T &
SuperType 。
我希望T &
SuperType會簡化為T
因為每個T都是SuperType但類型檢查器不會進行這種簡化。
interface I {
readonly isI: true
}
class Base {
// HACK: Having a private member forces nominal typing
private readonly isBase: true = true;
toString() {
// Use the private field to quiet warning.
return `isBase=${this.isBase}`;
}
}
class Sub extends Base implements I {
readonly isI: true = true
}
// Custom type guards.
function isI(x: unknown): x is I {
return typeof x === 'object' && x !== null && 'isI' in x &&
(x as ({['isI']: unknown}))['isI'] === true;
}
function isBase(x: unknown): x is Base {
return x instanceof Base;
}
// Intersect with an inferred type parameters.
function andI<T>(x: T): T & I {
if (isI(x)) {
return x;
} else {
throw new Error();
}
}
function andBase<T>(x: T): T & Base {
if (isBase(x)) {
return x;
} else {
throw new Error();
}
}
let sub = new Sub();
let subAndI = andI(sub); // Has type (Sub & I)
let subAndBase = andBase(sub); // Has type (Sub & Base)
// Sub and (Sub&I) are mutually assignable
let sub2: Sub = subAndI;
subAndI = sub2;
TS 操場編譯這個沒有錯誤也沒有警告。 這let sub2: Sub = subAndI
似乎通過暗示我但是類型推斷(在“.D.TS”選項卡中)包括:
declare let sub: Sub;
declare let subAndI: Sub & I;
declare let subAndBase: Sub & Base;
不,正如我所料
declare let sub: Sub;
declare let subAndI: Sub;
declare let subAndBase: Sub;
Sub&I
似乎可以與Sub
相互分配,但是將兩者視為 TS 中的等效類型是否存在一些風險?
我可以在microsoft/TypeScript#16386中找到與該主題最接近的記錄討論,以及我在 2017 年提交的問題,要求 TypeScript 實施所謂的吸收定律,即如果T extends U
,則T | U
T | U
將始終減少為U
並且T & U
將始終減少為T
。
我的問題最終以“固定”和“拒絕”的形式同時關閉。 這種行為中的一些(但不是全部)最終得到了實施。 在那個問題中從未明確說明為什么沒有完全實施交叉路口減少,所以接下來是我的(希望是受過教育的)推測。
TL;DR:這種全面的簡化要么會破壞人們所依賴的東西,要么在 TS 開發團隊時間或編譯器性能方面不值得。
如果 TypeScript 的類型系統是完全健全的,並且如果類型簡化是 TypeScript 設計的主要考慮因素,那么這些法則可能應該得到實施。 但是,盡管類型系統的健全性是 TS 開發的指導原則之一,但它並不是唯一的。
TypeScript 的類型系統並不完全健全,也並非如此(請參閱TypeScript Design Non-Goal #3以及microsoft/TypeScript#9825中的討論)。 因此,一些關於類型的“顯然是正確的”法律無法在不破壞其他事物的情況下實施。
例如,在健全的類型系統中,子類型是可傳遞的,因此T extends U
和U extends V
將暗示T extends V
。 但在 TypeScript 中並非總是如此。 可選屬性以不合理但有用的方式表現,因此{a?: string} extends {}
和{} extends {a?: number}
,但不是{a?: string} extends {a?: number}
。 在健全的類型系統中,交點是可交換的,因此T & U
應該與U & T
的類型相同。 唉,這又是這樣,在 TypeScript 中並不總是如此。 具有多個調用簽名的重載函數是順序相關的,因此{(): string} & {(): number}
與{(): number} & {(): string}
是不同的類型。 對此所做的任何更改都必須仔細考慮,以免破壞大量現實世界的代碼。
為了簡化而簡化也不是 TypeScript 的目標。 例如,當人們初始化具有編譯器將完全忘記的屬性的對象時, 過多的屬性檢查會發出警告,因為它通常表示編程錯誤。 因此const x: {a: string} = {a: "", b: 0}
會產生警告,因為不會跟蹤b
屬性。 並且可能是一個錯誤。 另一方面, const x: {a: string} | {a: string, b: number} = {a: "", b: 0}
const x: {a: string} | {a: string, b: number} = {a: "", b: 0}
不會產生警告,因為編譯器會認為可能存在b
屬性。 但是{a: string} | {a: string, b: number}
如果在任何地方都嚴格應用吸收定律, {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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.