Why cannot Typescript infer the type of the function argument here while Intellisense can?
Solution 1:
See microsoft/TypeScript#17520, microsoft/Typescript#25092, microsoft/TypeScript#38872, and microsoft/TypeScript#44999 for examples of similar problems. The compiler uses heuristics to infer types, and sometimes the compiler gives up before everything has been successfully inferred.
In your call to registerHandlerMap()
, you want the compiler to infer both the generic type parameter HandlerName
and the contextual type of the event
callback parameter.
Theoretically, a so-called "full unification algorithm" to perform type inference, as discussed in microsoft/TypeScript#30134, would be guaranteed to find both "network-event"
for HandlerName
and Event
for typeof event
. But the TypeScript compiler does not use such an algorithm for inference.
Instead it uses some heuristic rules, that have some advantages like acceptable performance and ability to do partial inferences from partial code (see this comment on a related issue). These inference rules work pretty well in practice but there are definitely limitations where it gives up. One situation where the compiler tends not to do things well is when you have multiple type dependencies on a single object, like the map
parameter passed to registerHandlerMap()
.
What happens here, more or less, is that the compiler makes two inference phases. The first phase inspects each "context-insensitive" function parameter where any callback parameters are annotated); the compiler tries to use these to infer generic type parameters for each one. The second phase then inspects each "context-sensitive" function parameters and tries to infer any remaining generic type parameters and the contextual type of any unannotated callback parameters.
You have exactly one function parameter (map
), and it is context sensitive. So you get only one inference pass, where the compiler needs to infer HandlerName
and typeof event
at the same time. It successfully infers HandlerName
from the defaultHandler
property, but at this time it still has no idea what typeof event
is. It could only possibly know this after HandlerName
is inferred, but this is happening at the same time.
And then inference is over. The compiler has inferred "network-event"
for HandlerName
, but inference for typeof event
has failed. It falls back to any
, and you get an error if you've enabled the --noImplicitAny
compiler option. If the compiler could make one more pass, it would presumably be able to infer Event
for typeof event
. But there is no more processing time to be spent here, unfortunately. The compiler is done with inferring types for that function call.
So then, what happens when you use IntelliSense? Well, you are asking your IDE to show you type information about the call to registerHandlerMap()
that the compiler has determined. At this point, you get a little more processing time while your IDE plugs in the inferred 'network-event'
for HandlerName
:
// on hover
// const registerHandlerMap: <"network-event">(
// map: HandlerMap<"network-event">
// ) => void
And therefore depending on how you inspect the code, the compiler will be able to tell you things that assume map
is a HandlerMap<"network-event">
. When you blank out event
with {}
and ask the compiler for property names to destructure from the callback parameter, the compiler uses this inference to prompt you for things appropriate for Parameters<HandlerMap<"network-event">["network-event"]>
. But this extra bit of processing after compilation has completed doesn't fix the inference problem. That has already failed, and will only succeed if you (for example) annotate the callback parameter.