由于與問題無關的原因(但包括型別級編程中的樂趣和利潤),我的一種型別最終歸結為以下最小示例:
type IsTrue<A extends true> = A
type Refl<M> = M extends M ? true : false
type Proof<M> = IsTrue<Refl<M>>
...這會導致編譯錯誤。現在,我們可以討論一下我是如何走到這一步的,這與(可能)錯誤的 Type Equality 編碼方式有關。但問題仍然存在:什么時候不能解決M extends M所有true問題M?反例是什么?如何解決這個問題(可能通過約束M)?
uj5u.com熱心網友回復:
最初在microsoft/TypeScript#21316中實作的條件型別并不總是急切地評估。如果它們依賴于尚未指定的泛型型別引數,例如定義M的主體內部Refl<M>,則編譯器通常會推遲對條件型別的評估。請注意,關于編譯器評估條件型別的確切時間和地點的詳細資訊沒有在任何檔案中詳細說明,并且它們隨著時間的推移隨著 TypeScript 的新版本而發展,所以我在這里不能絕對肯定地說任何話。但總體情況正如我所描述的那樣。
您期望編譯器查看M extends M ? true : false并急切地將其簡化為,true以便您的定義type Refl<M> = true在 的定義內Refl<M>或 的定義內等同于Proof<M>。這些評估都沒有發生;它們被推遲,因為在這兩種情況下M都是未指定的型別引數。所以編譯器不能確定它Refl<M>會比聯合 true | false(也稱為型別)更窄,boolean因此不知道滿足.IsTrue<A extends true>
因此,它實際上不M extends M ? true : false應該評估為false(據我所知它不能),而是編譯器根本無法評估它以將其簡化為true.
我沒有看到任何關于這種特定情況的 GitHub 問題,但是有許多這樣的問題歸結為編譯器無法分析依賴于未解決的泛型型別引數的條件型別。有關相對較新的示例,請參閱microsoft/TypeScript#46795。
請注意,普通泛型型別引數的特定形式稱為分布式條件型別,因此M extends ... ? ... : ...其中的任何聯合都將在評估之前分解為單獨的成員。這不會影響是否可以比 更寬,但會影響輸出型別:MMRefl<M>true
type Refl<M> = M extends M ? true : false
type Hmm = Refl<never> // never
type Refl2<M> = [M] extends [M] ? true : false;
type Hmm2 = Refl2<never> // true
Refl<M>分布于 中的聯合M,并被never認為是“空聯合”(參見ms/TS#23182 中的此評論),因此輸出也是空聯合。但是Refl2<M>不是可分配的(因為[M]不是普通的泛型型別引數)Refl2<never>,true. 但是,兩者never和true都可以分配給true,所以IsTrue<Refl<M>>無論如何都可以解決。但這比它看起來要證明的要棘手。
可以想象,可以引入一個功能,在不依賴于(您的情況)或不是普通泛型型別引數(不是您的情況)的情況下,X extends X ? Y : Z可以急切地減少表單的條件型別。但是這樣的特性會對編譯器性能產生負面影響,因為即使絕大多數條件型別都不是這樣,它也需要檢查每個條件型別以適應這種情況;功能需要自己付費,而這個可能不會。更糟糕的是,可能有很多現實世界的代碼有意或無意地依賴于編譯器延遲這樣的條件型別,這樣的特性將是一個巨大的突破性變化。YYXX
最后,如果您只是對解決方法感興趣,我通常的方法如下:如果您確定T extends U但編譯器不是,那么您不能T在期望分配給U. 但是你可以使用Extract<T, U>. Extract<T, U>實用程式型別主要用于過濾任何聯合,T以便只U留下那些可分配的成員。但是編譯器確實看到Extract<T, U>兩者都可以分配T和U(可能在ms/TS#29437中),如果你是對的T extends U,那么Extract<T, U>最終將評估T為所需的值。所以這個標簽外的使用Extract做我們想要的:
type Proof<M> = IsTrue<Extract<Refl<M>, true>> // okay
This is almost like a type-level type assertion, so similar caveats apply. If you're wrong about Refl<M> being assignable to true, then IsTrue<Extract<Refl<M>, true>> would still compile, but you're no longer evaluating IsTrue<Refl<M>>, but something more like IsTrue<never> or IsTrue<true>. So be careful!
Playground link to code
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/426117.html
