Description
TypeScript Version: 3.9.2
Search Terms:
- typescript exhaustiveness check for single tag
- typescript exhaustiveness check for single option
- typescript switch case on tagged union with single item
- typescript type guards for tagged union of single item
Expected behavior:
No compile errors: we cannot get the default
branch, so type of x
in there should be narrowed down to never
, and the function test
is correct.
Actual behavior:
Compile error at line 20 (return absurd(x);
):
Argument of type 'Disjoint' is not assignable to parameter of type 'never'.
However, if we uncomment the code that adds the second item to Disjoint, it will be working properly.
Code
enum Labels {RED, YELLOW}
type Disjoint
= { label: Labels.RED; value: number }
// | { label: Labels.YELLOW; value: string}
;
const absurd = (u: never): never => { throw new Error() };
function test(x: Disjoint): number {
switch (x.label) {
case Labels.RED:
return x.value;
// case Labels.YELLOW:
// return +x.value;
default:
return absurd(x);
}
}
This may seem a weird corner-case, but I have stumbled this behaviour multiple times. You can have a tagged union of single tag when you plan to extend it later. When you want to ensure that later when you add new tags to it, you will get compile errors in every non-exhaustive switch statement that must be exhaustive.
Output
"use strict";
var Labels;
(function (Labels) {
Labels[Labels["RED"] = 0] = "RED";
Labels[Labels["YELLOW"] = 1] = "YELLOW";
})(Labels || (Labels = {}));
const absurd = (u) => { throw new Error(); };
function test(x) {
switch (x.label) {
case Labels.RED:
return x.value;
// case Labels.YELLOW:
// return +x.value;
default:
return absurd(x);
}
}
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"declaration": true,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}
Playground Link: Provided