Skip to content

No implicit returns error in exhaustive if statement #17358

Description

TS 2.4.1 with noImplicitReturns enabled

    class A {}
    class B {}

    // error: Not all code paths return a value.
    const fn = (x: A | B) => {
        if (x instanceof A) {
            x // A
            return 1
        } else if (x instanceof B) {
            x // B
            return 2
        } else {
            x // never
        }
    }

I would not expect this to error because the if statement is exhaustive, therefore all code paths do return a value.

Activity

  1. j-oliveras commented on Jul 22, 2017

    @j-oliveras
    Contributor

    Is not exhaustive you can call fn function as fn({}) or fn({ x: 1 }). These calls are valid and are not A nor B.

    So, I think, that works as intended.

  2. OliverJAsh commented on Jul 22, 2017

    @OliverJAsh
    ContributorAuthor

    Jordi Oliveras Rovira (@j-oliveras) I think what you're saying makes sense, but I don't see how that applies to this example:

        interface A { type: 'A' }
        interface B { type: 'B' }
    
        // error: Not all code paths return a value.
        const fn = (x: A | B) => {
            if (x.type === 'A') {
                x // A
                return 1
            } else if (x.type === 'B') {
                x // B
                return 2
            } else {
                x // never
            }
        }

    If using a switch statement instead of the if, the error goes. I would like to understand why we get this error when using if statements.

  3. jcalz commented on Jul 23, 2017

    @jcalz
    Contributor

    Jordi Oliveras Rovira (@j-oliveras) It is exhaustive as far as the type checker is concerned, since it infers a never type for x in the final else clause. Of course, as you note, you can fool it, due to an inconsistency between the nominal typing of instanceof at runtime and the structural typing of TypeScript (see, e.g., #11664). But that's not the issue here, as Oliver Joseph Ash (@OliverJAsh)'s followup demonstrates.

    The simplest repro I can imagine is something like:

    function foo(x: boolean) {
      if (x) return 1;
      if (!x) return 2;
      // x is inferred as never here, but ts still thinks some paths don't return a value
    }

    I'm guessing that the real issue is that the control flow analysis just isn't clever enough to realize that, once it has narrowed something to the never type, the surrounding code is unreachable and shouldn't count as a code path for the purposes of noImplicitReturns. Obviously there are workarounds (rework the if/else clauses to be obviously exhaustive to the compiler; throw an exception in any code you know is unreachable; etc.) but the question is whether the reported issue is a design limitation or a bug. I don't think it should be intended behavior.

  4. DanielRosenwasser commented on Jul 24, 2017

    @DanielRosenwasser
    Member

    I believe the problem is that only switch statements are special cased here. Basically, if the last statement in a function is a switch statement, we do an extra check for exhaustiveness, but we decided that if/elses might be too complex of a check. You'd have to check with Anders Hejlsberg (@ahejlsberg) for specifics.

    You can always return the result of calling assertNever (documented here, available on npm here.

  5. mhegazy commented on Aug 17, 2017

    @mhegazy
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  6. OliverJAsh commented on Oct 19, 2017

    @OliverJAsh
    ContributorAuthor

    This also affects switch statements when they exist within an if statement. You can workaround it by wrapping the switch in an IIFE.

    Is there anything TS can do to help here?

    {
        enum Input { foo, bar }
    
        // Unexpected error: Not all code paths return a value.
        const fn = (input: Input) => {
            if ('foo') {
                switch (input) {
                    case Input.bar: return 0;
                    case Input.foo: return 1;
                }
            } else {
                return 1
            }
        }
    
        // Workaround: wrap switch in IIFE
        const fn2 = (input: Input) => {
            if ('foo') {
                return (() => {
                    switch (input) {
                        case Input.bar: return 0;
                        case Input.foo: return 1;
                    }
                })();
            } else {
                return 1
            }
        }
    }
  7. jcalz commented on Oct 19, 2017

    @jcalz
    Contributor

    Oliver Joseph Ash (@OliverJAsh) said:

    Is there anything TS can do to help here?

    Daniel Rosenwasser (@DanielRosenwasser) said:

    You can always return the result of calling assertNever (documented here, available on npm here.)

  8. OliverJAsh commented on Oct 19, 2017

    @OliverJAsh
    ContributorAuthor

    Joe Calzaretta (@jcalz) Thanks, that works.

    TypeScript already special cases switch statements, as mentioned by Daniel Rosenwasser (@DanielRosenwasser), and so I was wondering if it could extend this behaviour to switch statements within if statements?

  9. eucaos commented on Nov 27, 2017

    @eucaos

    I have the same problem with nested switch clauses :
    const reducer = (state: "A", action: "act1"): "A" => { switch (state) { case "A": switch (action) { case "act1": return state; } } };
    I get function lacks ending return statement error message.

  10. locked and limited conversation to collaborators on Jun 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions