Skip to content
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

Inconsistent inferred type predicate for filter #61182

Open
pedrolamas opened this issue Feb 15, 2025 · 3 comments
Open

Inconsistent inferred type predicate for filter #61182

pedrolamas opened this issue Feb 15, 2025 · 3 comments

Comments

@pedrolamas
Copy link

πŸ”Ž Search Terms

filter type predicate

πŸ•— Version & Regression Information

This only makes sense since TS5.5, when inferred type predicates were introduced.

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAhjAvDA2gcigU2mgNDMAVwBtiBdAKAHoqY6YA9AfgotElgCMl4A6AMwCWxLACcAFAA8kAPhjSAhImRFSASmq16zVu2gxgPOAOFips+TCUqSxGADJ783tDiioEAOqCoAC3EYaGoaNPSMLBRAA

πŸ’» Code

const a = ['test', null]

const b = a.filter(x => x !== null)

const c = a.filter(x => x !== null && x.startsWith('t'))

πŸ™ Actual behavior

On the above, the type of b is correctly inferred as string[], but the type of c is incorrectly inferred to (string | null)[]

πŸ™‚ Expected behavior

The inferred type of c should string[], just like the inferred type of b.

Additional information about the issue

No response

@MartinJohns
Copy link
Contributor

MartinJohns commented Feb 15, 2025

This is working as intended. Only for specific cases the type guard will be inferred. See #57465, the "Interesting cases" section.

The type guard must be true for all values of the narrowed type, but your second function does not fulfill that purpose. The related issue to tackle this limitation is #15048.

Here's an example of the problem: Playground link

const fn = (x: string | null): x is string => x !== null && x.startsWith('t')

declare const value: string | null
if (fn(value)) {
    value;
    // ^?
}

if (!fn(value)) {
    value;
    // ^?
}

In the second if-check the type is narrowed to null, because the type guard checks if the value is a string - and if it's not it must be null. Note that there's nothing special about the function being passed to filter.

@pedrolamas
Copy link
Author

Thanks for your comment @MartinJohns, I guess the main problem is the "else" of the type inference, as it can still be a string.

I think that .filter() is indeed a special case, as the "else" inference is irrelevant (it excludes any value that would make the predicate false), but I guess that currently that is not possible given how inferred predicates work.

Manual type inference it is then!

const c = a.filter((x): x is string => x !== null && x.startsWith('t'))

@MartinJohns
Copy link
Contributor

I think that .filter() is indeed a special case

The team (which I'm not part of, just pointing out) does not like to hardcode special cases. This would lead to confusion, as people would expect the same behavior for their own types and implementations. Think of a filter() function for an Observable implementation. It would be very confusing if the inference for type guards would work different here from the Array.filter() case.

So without #15048 this remains unsolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants