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

Callback parameter inference not working when part of an inferred tuple type #52047

Open
artysidorenko opened this issue Dec 29, 2022 · 4 comments
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@artysidorenko
Copy link

artysidorenko commented Dec 29, 2022

Bug Report

Been experiencing the below problem with no-implicit-any on callback parameter types, when the callback is part of a tuple function-argument, whose type I am inferring.

Was thinking it could be a case of unsupported/incorrect usage on my end, however the type suggestion does appear to be correct on-mouse-hover, so wondering if there's something extra at play.

Might be somewhat related to #44476 but not sure.

🔎 Search Terms

Tuple, callback, generic, inference

🕗 Version & Regression Information

This is the behavior in every version I tried since 4.0 that brought support for variadic tuples, and I reviewed the FAQ for entries about generics/tuples/callbacks

⏯ Playground Link

Playground link with relevant code

(for context, if the above example feels a bit contrived: the actual use-case involves typing/checking a function argument that can be a variadic tuple with multiple complex elements, using a more complex utility type. If interested here's a second playground link with the whole shebang - but in the first one I've tried to pare it down to the most basic example with just 1 element on the tuple)

💻 Code

type Options<T = unknown> = {
  inputData: T;
  handler: (t: T) => void
}

type ElemOptions<T> = T extends { inputData: infer X } ? Options<X> : never

type TupleOptions<
  T extends any[],
> = T extends [infer Elem]
  ? [ElemOptions<Elem>]
  : T
  
function fooWithTuple<T extends any[]>(bar: readonly [...TupleOptions<T>]) {
  return
}

fooWithTuple([{
  inputData: 'string',
  // Error: Parameter 'x' implicitly has an 'any' type.
  // even though hovering over `handler` correctly labels it as `handler: (t: string) => void`
  handler: (x) => {}
}])

// included in the playground link but not shown here:
//  (1) type-inference all works fine when you're not dealing with a tuple, and
//  (2) type-checking in a tuple works correctly when you add the param type explicitly

🙁 Actual behavior

Handler complains about no-implicit-any, even though type-suggestion work as expected, and type-checking works if you add the param type explicitly.

🙂 Expected behavior

Handler parameter type can be inferred automatically; no no-implicit-any complaints.

@Andarist
Copy link
Contributor

You can implement this one with reverse mapped types: TS playground

@artysidorenko
Copy link
Author

You can implement this one with reverse mapped types: TS playground

Discussed a bit in an external thread - mapped types may not work for the use case where each element in the tuple has multiple type parameters to infer (updated the second playground link in the issue description to reflect this example)

@Andarist
Copy link
Contributor

As mentioned in that other thread - I strongly believe that this proposal would make it way easier to implement such types. With that in place, we could reuse the "reverse mapped types" technique presented in the playground above to infer multiple "generics" per tuple element (where each "generic" would just be a property of the inferred element).

@Andarist
Copy link
Contributor

This one will be extremely hard to fix for outside contributors without a deep understanding of the internals of inference, contextual typing, etc.

The problem is that a source object with context-sensitive functions starts as a NonInferrableType. Nothing can be inferred from it so it falls back to the constraint and that's just any[] here. The function's parameters are assigned from within checkExpressionWithContextualType based on the contextualSignature - but there is none since it couldn't infer anything so far as the source was "non-inferrable" and so it is forced to assign any to those parameters and it can never come back to this place in an attempt to reassign them (likely because other inferences that are made after that could depend on this assigned information).

Fixing this would require the compiler to become smarter about "partially inferrable types". I actually managed to fix this locally but, as I expected, the anyFunctionType appeared in the output of some tests where it shouldn't (it "leaked"). I think that perhaps it would be possible to allow those partially inferrable types to propagate as inference candidates a little bit but the compiler would have to know when and how to discard them if needed (or something like that).

It's actually interesting here that reverse-mapped types can infer from partially inferrable types (isPartiallyInferableType is literally only used by createReverseMappedType). So they could be a viable alternative here but for them to be usable by @artysidorenko's use case (which isn't presented by this issue here in full) some improvements for reverse mapped types would have to be made:
#52095
#53017 (this isn't strictly required to satisfy the use case but it would allow to type this stuff in an easier way, the alternative to this is inferring to interested reverse mapped types and that's not as readable)
#52062 (#52095 is a more general take on improvements to getTypeOfPropertyOfContextualType and it - almost - eliminates the need for this PR, the only added benefit of this PR over #52062 is an extra fix targeted at tuples)
#54029

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
Development

No branches or pull requests

3 participants