我做了一些演示来重现我的问题。他们来了。
问题 1:为什么输入 g
会得到 2
?
// 1
type f = [never] extends [infer S1, ...infer S2]
? ([S1] extends [never] ? 1 : 2)
: 3
// why 2
type g = [never] extends [infer S1, ...infer S2]
? ([never] extends [S1] ? 1 : 2)
: 3
在我看来,输入 f
等于 g
,因为 S1
是 never
。
问题 2:为什么输入 h
会得到 2
?
// 1
type i = [never] extends [infer S1]
? ([never] extends [S1] ? 1 : 2)
: 3
// why 2
type h = [never] extends [infer S1, ...infer S2]
? ([never] extends [S1] ? 1 : 2)
: 3
在我看来,类型 i
等于 h
,因为 S2
未使用。
问题 3:为什么输入 k
会得到 never
?
// 1
type j = [never] extends [infer S1, ...infer S2]
? (never extends S1 ? 1 : 2)
: 3
// why never ?
type k = [never] extends [infer S1, ...infer S2]
? (S1 extends never ? 1 : 2)
: 3
我一点也听不懂。我认为 k
至少应该是 1
2
3
之一。
答案 0 :(得分:4)
您的问题 1 和 2 似乎暴露了 TypeScript 中的一个错误。
我还期待您的 F
、G
、H
和 I
类型(非原始类型通常用 UpperCamelCase 而不是 lowerCamelCase 编写,这使它们看起来像变量名) 计算为 1
。 G
和 H
评估为 2
的事实令人惊讶。我的研究表明这种行为是新的;您可以验证您的代码 works as expected in TypeScript 4.1.5,但是 fails in TypeScript 4.2.2。具体而言,此更改发生在 2021 年 1 月 12 日或 13 日左右。您的代码 works as expected in TypeScript 4.2.0-dev.20210112,但是 fails in TypeScript 4.2.0-dev.20210113。
这种行为很可能是在 microsoft/TypeScript#42248 合并到主代码分支时引入的。此拉取请求修复了一个错误 (microsoft/TypeScript#39992),但似乎导致了一些新错误,例如 microsoft/TypeScript#42331。
我已提交 microsoft/TypeScrpit#43213 来跟踪此问题。现在我想说的是,如果您有一个真实的用例需要像上面的 G
和 H
那样构建,并且您迫不及待地等待这个问题得到解决,那么您可以解决它通过使用类型别名而不是裸 never
,如下所示:
type N = never
type Gfixed = [N] extends [infer S1, ...infer S2] ? ([N] extends [S1] ? 1 : 2) : 3 // 1
您的问题 3 表现符合预期。在类型 K
中,(S1 extends never ? 1 : 2)
是 distributive conditional type,因为被检查的类型 S1
是一个类型参数。而这样的类型函数分布在联合上;意味着如果 F<T>
是可分配的,那么 F<X | Y>
将等价于 F<X> | F<Y>
。
这意味着 F<never>
应该是 never
(或者至少它是 F<T>
的所有可能答案的子类型)。为什么?因为X | never
等价于X
,所以F<X | never>
必须等价于F<X> | F<never>
。这意味着对于所有 X
,F<X> | F<never>
等价于 F<X>
。那么,从概念上讲,F<never>
应该是 never
。
事实证明,当在分布式条件类型中使用时,编译器实际上将 never
视为 "the empty union"。当我第一次遇到这种行为时,这种行为肯定让我感到惊讶,因为将 never
视为任何类型的联合都需要某种思维转变。
观察:
type Distributive<T> = T extends string ? { a: T } : { b: T };
type X = Distributive<"a"> // {a: "a"}
type Y = Distributive<0> // {b: 0}
type Z = Distributive<"a" | 0> // {a: "a"} | {b: 0}
type StillX = Distributive<"a" | never> // {a: "a"}
type Never = Distributive<never> // never