Covariant assignability of type guard functions is unsound #26981
Description
TypeScript Version: master (af8e44a)
Search Terms: user-defined type guard predicate assignable assignability unsound covariant invariant
Code
class A {
a: string;
}
class B extends A {
b: string;
}
function isB(x: {}): x is B {
return x instanceof B;
}
const isA: (x: {}) => x is A = isB; // allowed, should be compile error
const x = <A | string>new A();
if (!isA(x)) {
console.log(x.slice()); // runtime error
}
Expected behavior: Assignability error where marked.
Actual behavior: Successful compilation, runtime error.
Playground Link: link
Related Issues: #24865
If we want a kind of type guard function that is covariant, we need to not narrow when it returns false, i.e., a "true" result would be sufficient but not necessary for the value to be of the tested type. And we'd need a different syntax for the type of a type guard function that is necessary and sufficient compared to a type guard function that is sufficient but not necessary. One idea for the latter is (x: {}) => x is A | false
. (A type guard function that is necessary but not sufficient would then be (x: {}) => x is A | true
.) Getting all these variants supported would help us support conjunctions and disjunctions of type guard calls with other boolean expressions in #24865. If you like, this issue can be for the removal of the unsound assignability rule and I can file a separate suggestion for the new kinds of type guard functions.
Activity