Closed
Description
TypeScript Version: Nightly (v4.1.0-dev.20200807)
Search Terms: Tagged union, discriminated union, exhaustive checking
Code
TypeScript Playground Example
class Either<T> {
public static Left<T>(value: T): Either<T> {
return new Either('left', value);
}
public static Right<T>(value: T): Either<T> {
return new Either('right', value);
}
private constructor(
public readonly kind: 'left' | 'right',
public readonly value: T
) {}
}
function doSomething(): string {
const e = Either.Left('foo');
// e.kind: 'left' | 'right';
switch (e.kind) {
case 'left':
return e.value;
case 'right':
return e.value;
// case 'foo': as expected, this doesn't work
// break;
default:
const _exhaustiveCheck: never = e; // even though we've matched against all the possible cases of `kind`, compiler still complains here
throw new Error("How did we get here?");
}
}
However this can be made to work if we did this,
interface Left {
kind: 'left';
value: string;
}
interface Right {
kind: 'right';
value: string;
}
type Either = Left | Right;
Playground
Now doing switch (e.kind)
would perform the right checks.
Expected behavior:
Exhaustive type checking should work in the case with the class Either
and kind: 'left' | 'right'
.
Actual behavior:
Exhaustive type checking fails even though compiler understand the union only has 2 fields (e.g. case 'foo'
is an error).