-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Type-defined, fat-arrow function type guards do not compile #14826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
type foo =
-style type guards do not compiletype foo
- and interface-style type guards do not compile
type foo
- and interface-style type guards do not compile
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. 😄 Thanks again for the careful explanation! |
Currently, the behavior of type guards is asymmetric between
function
declarations 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
/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 theHasA
andHasB
type definitions (along with a bunch of others), then followed up by writing thehasA
andhasB
bodies.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.
The text was updated successfully, but these errors were encountered: