effective-typescript icon indicating copy to clipboard operation
effective-typescript copied to clipboard

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

Open utterances-bot opened this issue 3 years ago • 5 comments

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/

utterances-bot avatar Jul 04 '22 18:07 utterances-bot

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

arvabalazs avatar Jul 04 '22 18:07 arvabalazs

Similar question: this would be also the very same?

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

arvabalazs avatar Jul 04 '22 18:07 arvabalazs

@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 https://github.com/microsoft/TypeScript/issues/10571, you really do need to use the techniques described in this article to get partial inference.

danvk avatar Jul 05 '22 21:07 danvk

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

arvabalazs avatar Jul 06 '22 18:07 arvabalazs