打字稿:使用推断永远不会得到不同的结果

时间:2021-03-04 06:45:53

标签: typescript

我做了一些演示来重现我的问题。他们来了。

问题 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,因为 S1never

问题 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 之一。

1 个答案:

答案 0 :(得分:4)

您的问题 1 和 2 似乎暴露了 TypeScript 中的一个错误。

我还期待您的 FGHI 类型(非原始类型通常用 UpperCamelCase 而不是 lowerCamelCase 编写,这使它们看起来像变量名) 计算为 1GH 评估为 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 来跟踪此问题。现在我想说的是,如果您有一个真实的用例需要像上面的 GH 那样构建,并且您迫不及待地等待这个问题得到解决,那么您可以解决它通过使用类型别名而不是裸 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>。这意味着对于所有 XF<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

Playground link to code