Microsoft/TypeScript

Covariant assignability of type guard functions is unsound

Open

#26,981 opened on Sep 8, 2018

View on GitHub
 (3 comments) (3 reactions) (0 assignees)TypeScript (48,455 stars) (6,726 forks)batch import
CommittedHelp WantedSuggestion

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.

Contributor guide