Function parameter does not get inferred through target typing by a union through a layer of indirection
Bug Report
🔎 Search Terms
- optional function parameter inference
- optional method parameter type inference
- optional method inference indirection
- nullable method inference
- target type method union inference
- target type generic function argument
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about type inference
⏯ Playground Link
Playground link with relevant code
💻 Code
// Like in React.useCallback
declare function useCallback<T extends Function>(fn: T): T;
declare function ex1(callback: (x: number) => void): void;
declare function ex2(callback?: (x: number) => void): void;
declare function ex3(callback: ((x: number) => void) | 5): void;
ex1(x => {}) // OK. x: number
ex1(useCallback(x => {})); // OK. x: number
ex2(x => {}) // OK. x: number
ex2(useCallback(x => {})); // 7006 (no implicit any)
ex3(x => {}) // OK. x: number
ex3(useCallback(x => {})); // 7006 (no implicit any)
🙁 Actual behavior
The type of x in the code example above is not inferred when callback is a union or optional.
🙂 Expected behavior
x should be inferred as number.
Edit: simplified example
Here's what I believe is happening.
In the first case you "luck out" because the parameter type of ex1 is a function type. That is compatible with the constraint of the return type of useCallback, so useCallback can use it as a valid inference candidate. When x => {} needs to be contextually typed, it has an inference candidate from the returned contextual type.
In the second and third cases with ex2 and ex3, we look at the contextual type of useCallback() and find union types - undefined | ((x: number) => void) and 5 | ((x: number) => void) respectively. Neither is compatible with the constraint Function.
I do wonder if it would make sense to try to infer the intersection of the contextual type with the constraint itself.
In the meantime, one alternative way to write useCallback might be the following:
declare function useCallback<T>(fn: T & Function>): T & Function;
I attempted to filter the contextual type by applicable constraints and I also attempted to create an intersection out of those two in this PR. Even taking aside the correctness - the results were incredibly slow. Perhaps my method wasn't correct - this was quite a quick hack, just to check if I would get the correct results (and I did, for this case at hand - but not always for all of the tests).