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

Generic Tips Part 1: Use Classes and Currying to create new inference sites #22

Open
utterances-bot opened this issue Jul 4, 2022 · 5 comments

Comments

@utterances-bot
Copy link

Generic Tips Part 1: Use Classes and Currying to create new inference sites

Effective TypeScript: Generic Tips Part 1: Use Classes and Currying to create new inference sites

https://effectivetypescript.com/2020/12/04/gentips-1-curry/

Copy link

Hi

First of really good post!

I just wonder the first declaration

declare function getUrl<API, Path extends keyof API>(path: Path, params: ExtractRouteParams<Path>);

is equivalent with the following (assuming we do not need the Path type anymore directly)

declare function getUrl<API>(path: keyof API, params: ExtractRouteParams<typeof path>);

It would spare the whole hussle of all-or-nothing type interference (at least in this example).
You may add this for sake of completeness.

Thank you for the answer.
Disclaimer: I am pretty new to TS with strong C++ background. Sorry if the question is stupid

Copy link

Similar question: this would be also the very same?

function getUrl<API, Path=keyof API>(path: Path, params: ExtractRouteParams<Path>)

@danvk
Copy link
Owner

danvk commented Jul 5, 2022

@arvabalazs interesting question. Neither of those declarations is quite the same. For this declaration:

declare function getUrl<API>(path: keyof API, params: ExtractRouteParams<typeof path>): string;

is that typeof path is just keyof API, i.e. the union of all possible routes. It will never have a more specific type. In the example in the post, it will be "/users" | "/users/:userId". Because of the way that conditional types work in TypeScript (they distribute over unions), the net result is that the type of params can match any of the possible paths, not just the one you called getUrl with. Here's a playground link showing how this allows incorrect usage.

The second declaration has the same problem:

declare function getUrl<API, Path=keyof API>(path: Path, params: ExtractRouteParams<Path>): string;

In this case, if you omit the second type parameter (Path) then it's always set to keyof API. It won't be inferred as something more specific based on the value of path when you call getUrl. Here's a playground link showing this.

Until there's movement on microsoft/TypeScript#10571, you really do need to use the techniques described in this article to get partial inference.

Copy link

Thank you @dankv! Your explanation helps me a lot to build up my mental model about this language.

Copy link

Typescript 5.4 adds NoInfer, which is a much more ergonomic solution than currying

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

4 participants