Repository navigation
Exhaustiveness checking against an enum only works when the enum has >1 member. #23572
Description
Activity
- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Apr 20, 2018 - removedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Mar 7, 2019 This is also the case, when you try to prepare your code for future union types, but currently only use one type.
If you uncomment everything regarding the second interface (first 3 comment blocks) everything will work as intended.
interface Interface1 { type: "interface1"; } // interface Interface2 { // type: "interface2"; // } type UnionType = Interface1 // | Interface2; function calculateFromInterface(i: UnionType): number { switch (i.type) { case "interface1": return 0; // case "interface2": // return 1; } //Error: 'Argument of type 'Interface1' is not assignable to parameter of type 'never'.' //typescript version 3.5.1 //however the code can never get to here assertNever(i); } function assertNever(o: never) { throw "this should never happen"; }
Yeah, really it just comes down to "Exhaustiveness checks only work when discriminating against a union with size > 1"
Reacted by Ryan CavanaughI just ran into this bug too. Here is a simple example:
function assertNever(x: never): never { throw new Error("Unexpected object: " + x); } interface Square { kind: "square"; size: number; } type Shape = Square function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; default: assertNever(s); } }antoineprdhmm commented
on Feb 11, 2020 More actionsWe also ran into this bug today. Here is another code example, if it can help
const rejectUnexpectedValueOfPropertyInObject = ( objectName: string, propertyName: string, object: never ): never => { throw new Error( `Unexpected value for ${propertyName} in ${objectName}: ${ object && typeof object === "object" ? (object as any)[propertyName] : "<not an object>" }` ); }; type Result = | { outcome: "success"; } // | { // outcome: "error"; // reason: "reason_2"; // } | { outcome: "error"; reason: "reason_1"; }; const f = (a: number, b: number): Result => { if (a < 0) { return { outcome: "error", reason: "reason_1" }; } return { outcome: "success" }; }; const run = (a: number, b: number) => { const result = f(1, 2); if (result.outcome === "error") { switch (result.reason) { case "reason_1": console.log("error reason 1"); break; // case "reason_2": // console.log("error reason 2"); // break; default: rejectUnexpectedValueOfPropertyInObject("result", "reason", result); } } };
The error is
const result: { outcome: "error"; reason: "reason_1"; } Argument of type '{ outcome: "error"; reason: "reason_1"; }' is not assignable to parameter of type 'never'.By removing the comments for
reason_2in the type definition and in the switch, the error disappear.Also ran into this issue today when trying to structure some code for expansion:
1 remaining item
- addedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on Jul 23, 2025 - removedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on Jul 24, 2025 - addedDomain: check: Control FlowThe issue relates to control flow analysisThe issue relates to control flow analysis
on Oct 16, 2025 RyanCavanaugh commented
on Dec 15, 2025 MemberMore actionsNaive version of this is very bad (see PR #62114)
- addedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on Dec 15, 2025 - removedFix AvailableA PR has been opened for this issueA PR has been opened for this issue
on Aug 20, 2026 RyanCavanaugh commented
on Aug 31, 2026 MemberMore actionsDuplicate of #16976. Both reports concern a non-union object with a literal discriminant remaining assignable after every possible
switchcase has been handled, rather than narrowing tonever.- addedNeeds Human ReviewThis issue has a backlog check awaiting maintainer review.This issue has a backlog check awaiting maintainer review.
on Aug 31, 2026 - removedNeeds Human ReviewThis issue has a backlog check awaiting maintainer review.This issue has a backlog check awaiting maintainer review.
on Sep 23, 2026
TypeScript Version: typescript@2.9.0-dev.20180420
Search Terms: discriminated, exhaustiveness, type guard, narrowing
Code
Expected behavior: No error would be thrown, as the switch statement is exhaustive. If the ActionTypes.DECREMENT parts are uncommented (resulting in two possible values for ActionTypes) there is no error. An error only occurs when ActionTypes takes on a single value. The error occurs even if the
neverassertion happens in the default statement, which is obviously unreachable from IIncrement.Actual behavior: An error is thrown despite the only possible value being explicitly handled. If ActionTypes.DECREMENT is uncommented the expected behavior is present.
Playground Link: (fixed the links)
Error
Working
Related Issues:
#19904
#14210
#18056