Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upType-defined, fat-arrow function type guards do not compile #14826
Comments
|
The difference isn't about arrow functions vs regular function expressions/declarations; the only difference is that you have return type annotations in some places but not others: // OK
type HasB = (arg: ToCheck) => arg is B | A & B;
const hasB: HasB = (arg): arg is B | A & B => arg.hasOwnProperty('b');
// Error
const hasA: HasA = function(arg: ToCheck) {
return arg.hasOwnProperty('a');
}Without some return type annotation it's pretty sketchy to assume any boolean-returning function expression is actually a type guard for some type. |
|
I'm perplexed; this may be a failure of either what the docs say or my comprehension (and I'd be happy to write up a PR clarifying it once I actually understand this).
My reading of the docs suggested that these two were equivalent: // inline definition
const foo = (arg: number): string => arg.toString();
type Bar = (arg: number) => string;
const bar: Bar = (arg) => arg.toString();Your response indicates that's not the case; I'm not sure what the difference is between the two is. And that seems to be the root of my confusion here. To clarify: my mental model was that the use of the type ascription would inform the compiler in the same way that the general function type definition does, because I took the two as being substitutable/equivalent (leaving aside details about Allow me to ask the question another way: what exactly does this mean? type HasA = (arg: ToCheck) => arg is A | A & B;
const hasA: HasA = (arg: ToCheck): arg is A | A & B => arg.hasOwnProperty('a');And: is there no way to define a type-guarding function without duplicating that type ascription (or just not doing the initial type-driven approach)? Edit: I'll add: I understand that |
|
Pretty much a duplicate of #5951 but I'll post another longer comment while you read that |
They're very close. If you just had a naked function expression The end result for most types is not observably different, but contextual typing will not "downcast" a return type to a more-specific type (in this case,
If you wanted to write this without duplicating things, you could simply write: const hasA = (arg: ToCheck): arg is A | A & B => arg.hasOwnProperty('a');
type HasA = typeof hasA; |
|
This all makes sense. It's frustrating, but it makes sense, and I appreciate both the earlier-issue-link and your careful explanation here. (The frustration is because your offered solution doesn't actually address my approach at all: the concern is type-driven development, not having a type available when all is said and done. That you can do Sorry to duplicate the earlier issue; I went looking but both my Google and my GitHub searches failed to turn it up. Does this appear in the docs anywhere? If not, is there somewhere I should open a PR/edit a wiki? Otherwise, I can just write this up as a blog post. I just want a canonical, easy-to-find explanation all in one place for other folks perplexed by this. |
Currently, the behavior of type guards is asymmetric between
functiondeclarations and arrow-function declarations.TypeScript Version: 2.2.1
Code
This works:
Expected behavior:
So should this:
Actual behavior:
The compiler reports:
This is an unfortunate asymmetry. I prefer to use the
type/constbindings 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 theHasAandHasBtype definitions (along with a bunch of others), then followed up by writing thehasAandhasBbodies.The anonymity of the function is not at issue; given either of the above definitions, this works just fine:
Unsurprisingly, the same limitation exist with interface types—this does not work, either:
It would be great if type guards could be generalized to work with the type-definition and interface forms.