简体   繁体   中英

typescript intersection types of function

I am using typescript and faced some problems. The simplest demo would be:

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

I don't understand why type h is not never and param x in type i is not never

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

Also, didn't understand why type e is 2 not never ?

Note: non-primitive types are conventionally written in UpperCamelCase; this distinguishes them from primitive types and variable/property names. I will use this convention in what follows.

There's a bunch of pieces to this question, and so there will be a bunch of pieces to its answer.


Let's tackle the easy ones first:

type G = 1 & 2 // never

The reduction to never of empty intersections like 1 & 2 was implemented in microsoft/TypeScript#31838 and released with TypeScript 3.6. Before this, 1 & 2 was treated very much like never , in that you'd never be able to find a value that satisfies it. There's really no conceptual difference between 1 & 2 and never , although it's possible compiler implementation details will cause one to be treated differently from the other.


Next one:

type I = ((x: 1 & 2) => 0) // why x not never

The x is never , but the reduction is deferred until you actually use it:

type IParam = Parameters<I>[0]; // never

This deferral was implemented in microsoft/TypeScript#36696 and released with TypeScript 3.9. Before this, x was eagerly reduced to never just like G above.


Now for some more complicated examples:

type H = ((x: 1) => 0) & ((x: 2) => 0) // why H not never

There are actually quite a few reasons why H is not never :

  • The TypeScript-specific reason is because an intersection of function types is considered the same as an overloaded function with multiple call signatures:

     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

    Notice how h is displayed as a function with two overloads; one which accepts a 1 parameter, and another which accepts a 2 parameter. It does not accept 3 , and it also does not accept 1 | 2 1 | 2 even though from a pure type system perspective it probably should (see microsoft/TypeScript#14107 for a longstanding feature request to support this).

  • Even if TypeScript didn't interpret function-intersections as overloads, H should not be never . Function types are contravariant in their parameter types (see my answer to this question for an explanation. You can also read about the contravariance of function parameters in the TS handbook description of the --strictFunctionTypes compiler flag ). Contravariance turns things into their dual ; If F<T> is contravariant in T , then F<T | U> F<T | U> is equivalent to F<T> & T<U> , and F<T & U> is equivalent to F<T> | F<U> F<T> | F<U> . So a consistent type system, when asked for the parameter type of an intersection of functions would return the union of the function parameter types. And 1 | 2 1 | 2 is not never .

  • Even if H were a completely uninhabited type, currently TypeScript only reduces intersections to never in particular situations, and an intersection of functions is not one of those. If you look in the compiler source code file checker.ts it says "an intersection type is considered empty if it contains the type never , or more than one unit type or an object type and a nullable type ( null or undefined ), or a string -like type and a type known to be non- string -like, or a number -like type and a type known to be non- number -like, or a symbol -like type and a type known to be non- symbol -like, or a void -like type and a type known to be non- void -like, or a non-primitive type and a type known to be primitive." None of that says "two incompatible function types", so it is not reduced to never .


Last one:

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

Recall from earlier that an intersection of functions is considered to be an overloaded function. It is a known design limitation of TypeScript that when using type inference on an overloaded function type, the compiler does not try to resolve the overload by figuring out which call signature matches the intended inference best. It just uses the last call signature and ignores all the rest of them . In E , that means the compiler only sees (x: 2) => 0 when matching against (x: infer L) => 0 , and so L is inferred as 2 . The "right" thing to do would probably be the union 1 | 2 1 | 2 , but this does not happen. See microsoft/TypeScript#27027 for one instance of this limitation.


Playground link to code

in case

type h = ((x: 1) => 0) & ((x: 2) => 0)

you assign 1 as a type for x also known as literal type. Literal type is next to never smallest set of values. The difference between never and literal type 1 is that never is an empty set but 1 set which has exactly one value in it. So that never !== 1

here

type i = (x: 1 & 2) => 0; 

type of parameter x is never

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM