Description
Currently, the behavior of type guards is asymmetric between function
declarations and arrow-function declarations.
TypeScript Version: 2.2.1
Code
This works:
type A = { a: boolean };
type B = { b: string };
type ToCheck = A | B | A & B;
function hasA(arg: ToCheck): arg is A | A & B {
return arg.hasOwnProperty('a');
}
function hasB(arg: ToCheck): arg is B | A & B {
return arg.hasOwnProperty('b');
}
Expected behavior:
So should this:
type A = { a: boolean };
type B = { b: string };
type ToCheck = A | B | A & B;
type HasA = (arg: ToCheck) => arg is A | A & B;
const hasA: HasA = arg => arg.hasOwnProperty('a');
type HasB = (arg: ToCheck) => arg is B | A & B;
const hasB: HasB = (arg) => arg.hasOwnProperty('b');
Actual behavior:
The compiler reports:
Type '(arg: ToCheck) => boolean' is not assignable to type 'HasA'. Signature '(arg: ToCheck): boolean' must have a type predicate.
This is an unfortunate asymmetry. I prefer to use the type
/const
bindings throughout for various reasons, not least because it's extremely convenient for a type-driven design approach where I write the types out ahead of time and populate the fat-arrow function bindings later. E.g. in the case which motivated this, I did just that: I wrote out the equivalent of the HasA
and HasB
type definitions (along with a bunch of others), then followed up by writing the hasA
and hasB
bodies.
The anonymity of the function is not at issue; given either of the above definitions, this works just fine:
const hasBoth = function(arg: ToCheck): arg is A & B {
return hasA(arg) && hasB(arg);
}
Unsurprisingly, the same limitation exist with interface types—this does not work, either:
interface HasBoth { (arg: ToCheck): arg is A & B }
const hasBoth: HasBoth = (arg) => hasA(arg) && hasB(arg);
It would be great if type guards could be generalized to work with the type-definition and interface forms.