Constraints liberate, TypeScript edition
01 Apr 2022A little TypeScript puzzle I encountered recently:
interface Valid {
readonly isValid: true;
}
interface Invalid {
readonly isValid: false;
message: string;
}
type Validation = Valid | Invalid;
function report(validation: Validation) {
if (!validation.isValid) {
console.error("Invalid", validation.message);
}
console.info("Valid");
}
Looks fine right? And the TypeScript playground has no problem with
it.
But in my local environment, it was complaining that the message property
didn’t exist on validation, despite the fact that validation should be
narrowed to Invalid in the if branch.
Eventually, I figured out that the problem was due to the local environment not
having strictNullChecks set. And, if you make the effects of that explicit, by making the isValid fields optional:
interface Valid {
readonly isValid?: true;
}
interface Invalid {
readonly isValid?: false;
message: string;
}
you can reproduce the
error
in the TypeScript playground. The problem is, if isValid can be undefined in
Valid, then !isValid might be true for a Valid object, and so that
possibility can’t be excluded in the if branch.
It’s counterintuitive, though, that making the compiler less strict can make your program stop compiling. I suppose this is an example of how constraints liberate, liberties constrain. Making the compiler stricter means it knows more about what your program is doing, so it can prove more code is correct.