Typescript: Type inference and "never" returning function in strict mode

Created on 29 Dec 2017  ·  7Comments  ·  Source: microsoft/TypeScript

TypeScript Version: 2.7.0-dev.20171229

Code

// tsc --strict --noEmit test.ts

declare const throwError: () => never
declare const obj: { prop?: number }

if (!obj.prop) {
  throwError()
}
obj.prop.toFixed(1) // error TS2532: Object is possibly 'undefined'.

Expected behavior:
At the last line obj.prop is number, not undefined

Actual behavior:
At the last line obj.prop is possibly undefined

Duplicate

Most helpful comment

@jack-williams that would be expected behaviour, because the last line is reachable code. The issue relates to code that is not reachable.

@arusakov is suggesting that CFA should treat a function that returns never as if an error was thrown.

A better analogue would be:

// tsc --strictNullCheckts --noEmit test.ts
declare const obj: { prop?: number }
if (!obj.prop) {
    throw Error('Missing property');
}
obj.prop.toFixed(1); // no error, because this is not reachable

All 7 comments

~Not sure this is related to the never returning function, or the function at all. I get the same with~

// tsc --strictNullCheckts --noEmit test.ts
declare const obj: { prop?: number }
if (!obj.prop) {
}
obj.prop.toFixed(1) // error TS2532: Object is possibly 'undefined'.

Edit: Don't think my comment above is relevant, but I won't delete it and leave a dangling reference from the post below.

@jack-williams that would be expected behaviour, because the last line is reachable code. The issue relates to code that is not reachable.

@arusakov is suggesting that CFA should treat a function that returns never as if an error was thrown.

A better analogue would be:

// tsc --strictNullCheckts --noEmit test.ts
declare const obj: { prop?: number }
if (!obj.prop) {
    throw Error('Missing property');
}
obj.prop.toFixed(1); // no error, because this is not reachable

You're correct @kitsonk that I misread.

On the actual topic, is it safe to assume that constructing a value of type never always implies divergence?

Am I correct in saything that this example only really applies to top-level code where you can't just write return throwError();?

I believe never is always intended to be a bottom type and nothing more. Used in situations where either there cannot possibly be a value, or where TypeScript cannot possible infer a value type (e.g. const arr = []; in strict mode).

As you point out, it can easily be worked around by returning from a function:

// tsc --strict --noEmit test.ts

declare const throwError: () => never
declare const obj: { prop?: number }

(() => {
  if (!obj.prop) {
    return throwError()
  }
  obj.prop.toFixed(1)
})()

I understand that never is meant to be like bottom, but does bottom needs some defined semantics with statement composition (;)? If there are two statements S1 and S2, composed as S1;S2, and S1 has type never, then should S2 not be unreachable as it's predicated on S1 delivering a value?

@kitsonk
Very good example with throw new Error(). More real code looks like that:

function throwError() {
  throw new Error()
}

declare const obj: { prop?: number }

if (!obj.prop) {
  throwError()
}
obj.prop.toFixed(1) // error TS2532: Object is possibly 'undefined'.

Example in real life: ctx.throw() method from koa framework for node.js

Duplicate of #12825

Was this page helpful?
0 / 5 - 0 ratings

Related issues

CyrusNajmabadi picture CyrusNajmabadi  ·  3Comments

siddjain picture siddjain  ·  3Comments

jbondc picture jbondc  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

fwanicka picture fwanicka  ·  3Comments