Search code examples
typescriptunion-types

Union types: Typescript complains "Function lacks ending return statement and return type does not include 'undefined'.(2366)"


This is my code. I have a union type CellType. I assume the function will never be called with null, nothing, undefined or simular.

I do not want to have any else clause, or a final return statement, since I know all cases are handled. But somehow I am missing something. I have played around with the flags. I get the warning

"Function lacks ending return statement and return type does not include 'undefined'.(2366)" for the return value CellType

class Empty {
};

class MyError  {
    type!: number;
}

type CellType = number | string | boolean | Empty | MyError;

function plusOne(v: CellType): CellType {
    if (typeof v === 'number') {
        return v+1;
    }
    if (typeof v === 'string') {
        return -1;
    }
    if (typeof v === 'boolean') {
        return -1;
    }        
    if (v instanceof Empty) { return 1; }
    if (v instanceof MyError) { return v; }
    // return { 'type':-1}
}

console.log(plusOne(10));
console.log(plusOne("hej"));

Playground Link


Solution

  • What you want is for typescript to know that you've covered every case; this is called "exhaustiveness checking" and it's covered in the TypeScript docs.

    The docs recommend that you choose one of two approaches:

    The first is to compile with --strictNullChecks and make sure that every function where you want exhaustiveness checking is annotated with an explicit return type. In practice this isn't practical for most codebases.

    The second is to add a little function in a library somewhere called assertNever:

    function assertNever(x: never): never {
        throw new Error("Unexpected object: " + x);
    }
    

    Then in your code, use assertNever to indicate that all possible cases should have been covered, like so:

    function plusOne(v: CellType): CellType {
        if (typeof v === 'number') {
            return v+1;
        }
        if (typeof v === 'string') {
            return -1;
        }
        if (typeof v === 'boolean') {
            return -1;
        }        
        if (v instanceof Empty) { return 1; }
        if (v instanceof MyError) { return v; }
        return assertNever(v);
    }
    

    This will give you a compile error if you haven't covered every possible case by the time the assertNever is reached.