函数的 typescript 交集类型

xia*_*glu 4 typescript

我正在使用打字稿并遇到了一些问题。最简单的演示是:

type g = 1 & 2 // never
type h = ((x: 1) => 0) & ((x: 2) => 0) // why h not never
type i = ((x: 1 & 2) => 0)// why x not never
Run Code Online (Sandbox Code Playgroud)

我不明白为什么 typeh不是,而type 中的neverparam不是xinever

type e = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never; //  why e is 2 not never or 1?
Run Code Online (Sandbox Code Playgroud)

另外,不明白为什么typee不是?2never

jca*_*alz 8

注意:非基本类型通常采用大驼峰命名法(UpperCamelCase)编写;这将它们与原始类型和变量/属性名称区分开来。我将在下面使用这个约定。

这个问题有很多部分,因此它的答案也有很多部分。


让我们先解决简单的问题:

type G = 1 & 2 // never
Run Code Online (Sandbox Code Playgroud)

减少never空交集是在microsoft/TypeScript#318381 & 2中实现的,并随 TypeScript 3.6 一起发布。在此之前,的处理方式非常类似于,因为您永远无法找到满足它的值。和 之间实际上没有概念上的区别,尽管编译器实现细节可能会导致其中一个被区别对待。1 & 2never1 & 2never


下一个:

type I = ((x: 1 & 2) => 0) // why x not never
Run Code Online (Sandbox Code Playgroud)

是,x never减少会推迟到您实际使用它时:

type IParam = Parameters<I>[0]; // never
Run Code Online (Sandbox Code Playgroud)

此延迟在microsoft/TypeScript#36696中实现,并随 TypeScript 3.9 一起发布。在此之前,x急切地沦落为never如上G


现在来看一些更复杂的例子:

type H = ((x: 1) => 0) & ((x: 2) => 0) // why H not never
Run Code Online (Sandbox Code Playgroud)

其实有很多原因H不可以never

  • TypeScript 特定的原因是因为函数类型的交集被认为与具有多个调用签名的重载函数相同:

    declare const h: H;
    // 1/2 h(x: 1): 0
    // 2/2 h(x: 2): 0
    h(1); // okay
    h(2); // okay
    h(3); // error, no overload matches this call
    h(Math.random() < 0.5 ? 1 : 2); // error, no overload matches this call
    
    Run Code Online (Sandbox Code Playgroud)

    注意如何h显示为具有两个重载的函数;一个接受1参数,另一个接受2参数。它不接受3,而且它也不接受,1 | 2即使从纯类型系统的角度来看它可能应该接受(请参阅microsoft/TypeScript#14107以获取支持此功能的长期功能请求)。

  • 即使 TypeScript 没有将函数交集解释为重载,H也不应该如此never。函数类型在其参数类型上是逆变的(有关解释,请参阅我对此问题的回答。您还可以在TS 手册中关于--strictFunctionTypes编译器标志的描述中阅读有关函数参数逆变的内容)。逆变将事物变成对;如果F<T>是逆变的T,则F<T | U>等价于F<T> & T<U>,并且F<T & U> 等价于F<T> | F<U>。因此,一致的类型系统,当询问函数交集的参数类型时,将返回函数参数类型的并集。事实1 | 2并非如此never

  • 即使H 一种完全无人居住的类型,目前 TypeScript 也只会将交集减少到never特定情况,而函数的交集不是其中之一。如果您查看编译器源代码文件,checker.ts它会显示“如果交集类型包含类型never,或者多个单元类型或对象类型和可为空类型(nullundefined),或者string类似类型和类型,则该交集类型被视为空”已知不相似string,或number相似类型和已知不相似的类型number,或symbol相似类型和已知不相似的类型symbol,或void相似类型和已知不相似的类型是非void相似的,或者非原始类型和已知为原始的类型。” 这些都没有说“两种不兼容的函数类型”,因此它不会简化为never.


最后一个:

type E = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never;
//  why E is 2 not never or 1?
Run Code Online (Sandbox Code Playgroud)

回想一下之前,函数的交集被认为是重载函数。TypeScript 的一个已知设计限制是,当对重载函数类型使用类型推断时,编译器不会尝试通过确定哪个调用签名与预期推断最匹配来解决重载。 它只使用最后一个调用签名并忽略所有其余的签名。在 中E,这意味着编译器仅(x: 2) => 0在与 匹配时才会看到(x: infer L) => 0,因此L推断为2。“正确”的做法可能是成立工会1 | 2,但这并没有发生。有关此限制的一个实例,请参阅microsoft/TypeScript#27027 。


Playground 代码链接