Typescript: 'return' statement required after function that returns 'never'

Created on 22 Aug 2016  ·  10Comments  ·  Source: microsoft/TypeScript

TypeScript Version: 2.1.0-dev.20160819

Code

// Type representing the falsy values
type Falsy = null | undefined | false | 0 | ''; // No NaN type

function assert(value: Falsy, message: string): never;
function assert<T>(value: T, message: string): T;
function assert<T>(value: Falsy | T, message: string): never | T {
  if (!value) throw new Error(`AssertError: ${message}`);
  return value;
}

function foo(bar: number): string {

  // Unimportant
  if (bar === 0) return 'off';
  if (bar > 1) return 'on';

  assert(false, 'Invalid value for bar'); // because of 'false' argument, this will have return type 'never'

  // Error: Function lacks ending return statement and return type does not include 'undefined'
}

Expected behavior:
I would expect if the type system knows that assert(false, '') never returns, it doesn't require a return statement after assert.

Actual behavior:
Error on foo function:
Function lacks ending return statement and return type does not include 'undefined'

Design Limitation Question

Most helpful comment

The intended fix is

return assert(false, 'Invalid value for bar');

There are technical reasons why we can't figure this out correctly today but I'm not remembering exactly why right now. I'll follow up.

All 10 comments

Somewhat related: #8655

A simple fix is:

function foo(bar: number): string | undefined {

  if (bar = 0) return 'off';
  if (bar > 1) return 'on';

  assert(false, 'Invalid value for bar');
}

It does appear to essentially be a dupe of #8655 in the sense that this is valid:

function foo(bar: number): string {

  // Unimportant
  if (bar = 0) return 'off';
  if (bar > 1) return 'on';

  throw new Error('Invalid value for bar');
}

On the other hand, never says that there will not be a return, it does not contract to say that it will throw, which is what the code analysis is trying to determine.

@kitsonk
The problem is not related to the Error thrown. If the assert function is a function with an infinite loop:

function assert(): never {
    while (true) {
    }
}

the problem would be the same.
See: https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#the-never-type

The suggested solution adds | undefined to the signature of the function and requires unneeded null checks after calling the foo() function.

To satisfy the compiler, I think I'm better of with adding just a return statement:

function foo(bar: number): string {

  if (bar === 0) return 'off';
  if (bar > 1) return 'on';

  assert(false, 'Invalid value for bar'); // because of 'false' argument, this will have return type 'never'
  return undefined!; // never reached
}

I totally understand what the never type is. It is a bottom type, but it does not affect the flow control of the code, for some very important reasons. In fact, the "what is new" expresses the behaviour you are experiencing is by design:

// Function returning never must have unreachable end point
function error(message: string): never {
    throw new Error(message);
}

// Inferred return type is number
function move1(direction: "up" | "down") {
    switch (direction) {
        case "up":
            return 1;
        case "down":
            return -1; 
    }
    return error("Should never get here");
}

// Inferred return type is number
function move2(direction: "up" | "down") {
    return direction === "up" ? 1 :
        direction === "down" ? -1 :
        error("Should never get here");
}

// Inferred return type is T
function check<T>(x: T | undefined) {
    return x || error("Undefined value");
}

And to quote:

Because never is a subtype of every type, it is always omitted from union types and it is ignored in function return type inference as long as there are other types being returned.

The 'problem' I have is when there is code after a function that never returns.

The compiler warns that there is a code path that doesn't return a value.
I think the compiler warning is not needed there, because that code path will/can never be reached (at least if the function type signature doesn't lie)

The intended fix is

return assert(false, 'Invalid value for bar');

There are technical reasons why we can't figure this out correctly today but I'm not remembering exactly why right now. I'll follow up.

Thanx

How can I resolve these errors here?

[ts] Function lacks ending return statement and return type does not include 'undefined'.
[ts] Parameter 'conversation' implicitly has an 'any' type.
[ts] Not all code paths return a value.

By asking "how to use TypeScript" questions on StackOverflow or Gitter?

@kitsonk 💯

Was this page helpful?
0 / 5 - 0 ratings