簡體   English   中英

為什么 TypeScript 不簡化類型與其超類型之一的交集?

[英]Why does TypeScript not simplify the intersection of a type and one of its super-types?

有沒有辦法讓 TypeScript 的檢查器簡化交叉類型的不必要元素,或者我錯了它們是不必要的?

IIUC,類型SubType & SuperType等同於SubType ,但打字稿似乎沒有執行這種簡化。

如下所示,我將class Sub定義為class Baseinterface I的聲明子類型。

然后,我為每個采用<T>的超類型定義通用函數,如果它通過了該超類型的保護,則返回T & SuperType

我希望T & SuperType會簡化為T因為每個T都是SuperType但類型檢查器不會進行這種簡化。

鏈接到 TS 操場中的代碼

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 UU 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

Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM