Exhaustive checks not working correctly with switch statements
Closed this issue ยท 4 comments
๐ Search Terms
exhaustiveness, switch statement, discriminated union
๐ Version & Regression Information
- This is the behavior in every version I tried in the typescript playground, and I reviewed the FAQ for entries about the exhaustive checks in switch statements
โฏ Playground Link
๐ป Code
type SingleErr = {
_err:"SINGLE"
}
type MultiErr = {
_err:"FIRST"
} | {
_err:"SECOND"
};
function ThisFails(){
declare const e:SingleErr;
switch(e._err){
case "SINGLE":{
return "SINGLE_ERROR"
}
default:{
const exhaustive: never = e;
//^type is still inferred as SingleErr
throw new Error(`Unhandled: ${exhaustive}`)
}
}
}
function ThisWorks(){
declare const e:MultiErr;
switch(e._err){
case "FIRST":{
return "First error"
}
case "SECOND":{
return "Second error"
}
default:{
const exhaustive: never = e;
throw new Error(`Unhandled: ${exhaustive}`)
}
}
}
๐ Actual behavior
In ThisFails(), after handling the only possible case ("SINGLE"), TypeScript does not narrow the type to never in the default branch. The variable e retains its type SingleErr, causing a type error when trying to assign it to a never type.
๐ Expected behavior
TypeScript should recognize that all possible cases have been exhausted in the switch statement and narrow e to never in the default branch, consistent with how it handles multi-member discriminated unions. This would enable proper exhaustiveness checking for single-member discriminated types.
Additional information about the issue
This inconsistency makes it difficult to write exhaustiveness checks that work uniformly across discriminated types, regardless of whether they have one or multiple members. The workaround is to always use union types with at least two members, but this seems like a bug that could be solved in ts.
Duplicate of #18056.
๐ค Thank you for your issue! I've done some analysis to help get you started. This response is automatically generated; feel free to ๐ or ๐ this comment according to its usefulness.
Similar Issues
Here are the most similar issues I found
- (80%) microsoft/typescript#43056: Exhaustive check fails to narrow type when union has only 1 type
- (79%) microsoft/typescript#16976: narrowing in switch doesn't work with non-union types
- (77%) microsoft/typescript#18851: Switch on interface with single case not considered exhaustive
- (76%) microsoft/typescript#49334: Strange narrowing behavior on objects using discriminator property
- (76%) microsoft/typescript#33670: Inconsistency in narrowing behavior between non-union and union
- (76%) microsoft/typescript#29559: Unexpected exhaustive check result for switch statement
- (76%) microsoft/typescript#23572: Exhaustiveness checking against an enum only works when the enum has >1 member.
- (76%) microsoft/typescript#38963: Exhaustiveness check does not work for tagged union when there is the only tag
- (76%) microsoft/typescript#24412: Degenerated Union Type
- (76%) microsoft/typescript#59953: Switch typeguard for single case
- (75%) microsoft/typescript#45415: Switch not performing exhaustive check when a default with unreachable assert is specified
- (75%) microsoft/typescript#47288: Narrowing issue: exhaustive pattern matching on a "union" of only one type
- (75%) microsoft/typescript#58099: Function lacks ending return statement and return type does not include 'undefined'
- (74%) microsoft/typescript#20375: narrowing in switches doesnt work on constrained unions
- (74%) microsoft/typescript#32415: Question: How to test if is exhaustive? (getTypeAtArbitraryLocation?)
If your issue is a duplicate of one of these, feel free to close this issue. Otherwise, no action is needed.
I guess I should make a FAQ entry