-
Notifications
You must be signed in to change notification settings - Fork 12.8k
User-defined type guard behaves differently inside Array.filter
#30240
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
Related: #29501 |
The problem is here: Declaring this as declare const users: Array<BasicUserData & Partial<UserStats>>; causes the expected behavior. |
Can you elaborate on why? It seems there's a caveat in the type system here that I need to be aware of, so any information you can share would be appreciated! Also, I'm still not sure I understand why this behaviour isn't consistent between
IIUC, those types aren't entirely accurate, as In my contrived example this doesn't matter much because |
The issue here is that your filter predicate is not a valid type guard when instantiated. The refinement of a predicate needs to be a assignable to the input. You can see the issue here: type BasicUserData = { name: string };
type UserStats = { stats: {} };
declare const users: Array<BasicUserData | (BasicUserData & UserStats)>;
declare const checkHasUserStats: (user: typeof users[number]) => user is UserStats; // error The type guard does not typecheck if it's written as it would be used in the filter. TLDR: Using @RyanCavanaugh's solution the type guard overload is still not selected, but the type Writing your typeguard like this will give the desired behaviour I think. declare const checkHasUserStats: <T extends User>(user: T) => user is T & UserStats; |
@jack-williams That explains a lot I think, thank you. One question I have:
I instantiate the type guard with exactly the same type when using |
The difference (I believe) is that in the EDIT: My previous explanation was terrible. The input type to the type guard is fixed by the array type, which is |
@jack-williams Do you think any suggestions or bugs need to be filed as a result of what you're describing? I'm certain the behaviour I described is going to confuse users, especially when the behaviour differs between |
That achieves the desired behaviour, however it creates another problem: if we use a constrained generic then it seems we lose the ability to validate the type guard (our workaround for #29980): const checkHasUserStats = (user: User): user is UserStats => {
if ('stats' in user) {
// No error, good! :-)
const _test: UserStats = user;
return true;
} else {
return false;
}
};
const checkHasUserStatsGeneric = <T extends User>(user: T): user is T & UserStats => {
if ('stats' in user) {
// Error, bad! :-(
const _test: UserStats = user;
return true;
} else {
return false;
}
}; Do you have any suggestions for that? |
TypeScript Version: 3.3.3333
Search Terms: user defined type guard array filter narrow
Code
The text was updated successfully, but these errors were encountered: