Repository navigation
No implicit returns error in exhaustive if statement #17358
Description
Activity
j-oliveras commented
on Jul 22, 2017 ContributorMore actionsIs not exhaustive you can call fn function as
fn({})orfn({ x: 1 }). These calls are valid and are not A nor B.So, I think, that works as intended.
Reacted by Nathan Phillip BrinkOliverJAsh commented
on Jul 22, 2017 ContributorAuthorMore actionsJordi 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
switchstatement instead of theif, the error goes. I would like to understand why we get this error when usingifstatements.Jordi Oliveras Rovira (@j-oliveras) It is exhaustive as far as the type checker is concerned, since it infers a
nevertype forxin the finalelseclause. Of course, as you note, you can fool it, due to an inconsistency between the nominal typing ofinstanceofat 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
nevertype, the surrounding code is unreachable and shouldn't count as a code path for the purposes ofnoImplicitReturns. Obviously there are workarounds (rework theif/elseclauses 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.DanielRosenwasser commented
on Jul 24, 2017 MemberMore actionsI believe the problem is that only
switchstatements are special cased here. Basically, if the last statement in a function is aswitchstatement, we do an extra check for exhaustiveness, but we decided thatif/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.- addedDesign LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
on Jul 24, 2017 Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
OliverJAsh commented
on Oct 19, 2017 ContributorAuthorMore actionsThis also affects switch statements when they exist within an
ifstatement. 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 } } }
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.)OliverJAsh commented
on Oct 19, 2017 ContributorAuthorMore actionsJoe Calzaretta (@jcalz) Thanks, that works.
TypeScript already special cases
switchstatements, as mentioned by Daniel Rosenwasser (@DanielRosenwasser), and so I was wondering if it could extend this behaviour toswitchstatements withinifstatements?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.- locked and limited conversation to collaborators
on Jun 14, 2018
TS 2.4.1 with
noImplicitReturnsenabledI would not expect this to error because the if statement is exhaustive, therefore all code paths do return a value.