Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript never type inference

Can someone please explain me why given the following code:

let f = () => {     throw new Error("Should never get here"); }  let g = function() {      throw new Error("Should never get here"); }  function h() {      throw new Error("Should never get here"); } 

The following types are inferred:

  • f is () => never
  • g is () => never
  • h is () => void

I would expect the type of h to be () => never as well.

Thanks!

like image 272
Remo H. Jansen Avatar asked Oct 25 '16 23:10

Remo H. Jansen


People also ask

What is the never type in TypeScript?

TypeScript introduced a new type never , which indicates the values that will never occur. The never type is used when you are sure that something is never going to occur. For example, you write a function which will not return to its end point or always throws an exception.

Does TypeScript have type inference?

TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.

What is never [] in angular?

push('one'); We declared an empty array and it got assigned a type of never[] . This type represents an array that will never contain any elements (will always be empty). To solve the error, explicitly type the empty array. index.ts.

Does not exist on type never TypeScript?

The error "Property does not exist on type 'never'" occurs when we try to access a property on a value of type never or when TypeScript gets confused when analyzing our code. To solve the error, use square brackets to access the property, e.g. employee['salary'] .


1 Answers

Great question. The difference is that f and g are function expressions, where h is a function declaration. When a function is throw-only, it gets the type never if it's an expression, and void if it's a declaration.

Surely the above paragraph doesn't actually help. Why is there a difference in behavior between a function expression and a function declaration? Let's look at some counter-examples in each case.

Bad idea #1: Make throwing function expressions return void

Consider some code:

function iif(value: boolean, whenTrue: () => number, whenFalse: () => number): number {     return value ? whenTrue() : whenFalse(); } let x = iif(2 > 3,   () => { throw new Error("haven't implemented backwards-day logic yet"); },   () => 14); 

Is this code OK? It should be! It's common to write a throwing function when we believe the function shouldn't be called, or should only be called in error cases. If the type of the function expression were void, though, the call to iif would be rejected.

So it's clear from this example that function expressions which only throw ought to return never, not void. And really this should be our default assumption, because these functions fit the definition of never (in a correctly-typed program, a value of type never cannot be observed).

Bad idea #2: Make throwing function declarations return never

After reading the prior section, you should be saying "Great, why don't all throwing functions return never, then?"

The short answer is it turned out to be a big breaking change to do so. There's a lot of code out there (especially code predating the abstract keyword) that looks like this

class Base {     overrideMe() {         throw new Error("You forgot to override me!");     } }  class Derived extends Base {     overrideMe() {         // Code that actually returns here     } } 

But a function returning void can't be substituted for a function returning never (remember, in a correctly-typed program, never values cannot be observed), so making Base#overrideMe return never prevents Derived from providing any non-never implementation of that method.

And generally, while function expressions that always throw often exist as sort of placeholders for Debug.fail, function declarations that always throw are very rare. Expressions frequently get aliased or ignored, whereas declarations are static. A function declaration that throws today is actually likely to do something useful tomorrow; in the absence of a return type annotation, the safer thing to provide is void (i.e. don't look at this return type yet) rather than never (i.e. this function is a black hole that will eat the current execution stack).

like image 87
Ryan Cavanaugh Avatar answered Sep 20 '22 13:09

Ryan Cavanaugh