Skip to content

Function parameter does not get inferred through target typing by a union through a layer of indirection #50719

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

Open
TheUnlocked opened this issue Sep 11, 2022 · 2 comments · May be fixed by #58910
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@TheUnlocked
Copy link

TheUnlocked commented Sep 11, 2022

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

@DanielRosenwasser
Copy link
Member

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;

@Andarist
Copy link
Contributor

Andarist commented Feb 6, 2023

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
4 participants