Skip to content

Limited type inference of types infered from tuple (bigger then one arity) when using function overloads. #18898

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

Closed
mpawelski opened this issue Oct 2, 2017 · 5 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@mpawelski
Copy link

TypeScript Version: 2.5.3

I noticed that when we have function which has couple of overloads and where first argument use tuple then the types infered in tuple are not inferred properly in callback function that use this types.

Example:

declare function tupleTest<T1>(tuple: [T1], func: (arg: T1) => void): void;
declare function tupleTest<T1, T2>(tuple: [T1, T2], func: (arg: T1, arg2: T2) => void): void;
declare function tupleTest<T1, T2, T3>(tuple: [T1, T2, T3], func: (arg: T1, arg2: T2, arg3: T3) => void): void;
declare function tupleTest<T1, T2, T3, T4>(tuple: [T1, T2, T3, T4], func: (arg: T1, arg2: T2, arg3: T3, arg4: T4) => void): void;

tupleTest([1], arg1 => {
  arg1.toFixed(); // arg1: number
})
tupleTest(["asd"], arg1 => {
  arg1.toLowerCase(); // arg1: string
})

tupleTest([{ foo: "bar" }], arg1 => {
  arg1.foo; // arg1: { foo: string }
})

tupleTest([1, "a"], (arg1, arg2) => {
  arg1 //arg1: any
  arg2 //arg2: any
})
tupleTest([1, "b", { foo: 1 }], (arg1, arg2, arg3) => {
  arg1 //arg1: any
  arg2 //arg2: any
  arg3 //arg3: any
})

tupleTest([1, "b", { foo: 1 }, [1,2,3]], (arg1, arg2, arg3, arg4) => {
  arg1 //arg1: any
  arg2 //arg2: any
  arg3 //arg3: any
  arg4 //arg4: any
})

Expected behavior:
Callback function in overload that use tuple of bigger arity than one should have it's type inferred.

Actual behavior:
For overload with bigger tuple arity than one types are inferred as any

Strangely it happens only when function has overloads (like in above example). And only when I have overload that use tuple with one arity

typescript_tuple_inferencebug

After futher investigation it looks that there is a problem even when there is fuction overload without generic parameters:

typescript_tuple_inferencebug2


Real world use case:
I found this issue when I was learning about angular HttpClient testing and wondered if inject function could be more type-safe and infer parameters of callback function. I came up with something like this:

declare module "@angular/core/testing" {
  function inject(tokens: any[], fn: Function): () => any;
  function inject<T>(tokens: [Type<T>], fn: (injected1: T) => void): () => any;
  function inject<T1, T2>(tokens: [Type<T1>, Type<T2>], fn: (injected1: T1, injected2: T2) => void): () => any;
}

It works when you have signature with with 2-arity tuple but it breaks when there are other overloads.

(In this particular example from angular documentation you actually couldn't infer HttpTestingController because it's abstract class and it can't be assigned to Type<T>. I'm new to testing in angular but I guess if we could provide that nice type inference of tuples then maybe angular team would use not-abstract class or would InjectionToken<HttpTestingController> or something like that (I see that non generic Injector.get function is already deprecated so looks like they want to have stuff more type-safe and less "any"))

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Oct 2, 2017
@RyanCavanaugh
Copy link
Member

Order matters. Your more-specific overloads need to come first, because the overload resolution algorithm simply picks the first function that looks like it might works, then (incorrectly) applies a contextual type to the parameters before later undoing it.

declare function tupleTest<T1, T2, T3, T4>(tuple: [T1, T2, T3, T4], func: (arg: T1, arg2: T2, arg3: T3, arg4: T4) => void): void;
declare function tupleTest<T1, T2, T3>(tuple: [T1, T2, T3], func: (arg: T1, arg2: T2, arg3: T3) => void): void;
declare function tupleTest<T1, T2>(tuple: [T1, T2], func: (arg: T1, arg2: T2) => void): void;
declare function tupleTest<T1>(tuple: [T1], func: (arg: T1) => void): void;

I think @sandersn has a PR that fixes this, but in general the order should be the one above rather than the one you have

@mpawelski
Copy link
Author

mpawelski commented Oct 2, 2017

Aaaa.. Thanks! Good to know.

@RyanCavanaugh But it's still a bug right? Because it applies contextual typing from first overload but actually "uses" the later overload. I mean when I pressed F12(Go to definition) cursor goes to function with proper arity. That's why I thought it just skipped the previous functions when doing it's type inference and overload resolution magic.

@RyanCavanaugh
Copy link
Member

It's a limitation of the checking algorithm - once we've fixed a type for a parameter, we can't "go back", so the first contextual application (even of an any) is sticky. See also #11936.

@mhegazy
Copy link
Contributor

mhegazy commented Oct 3, 2017

#5453 tracks implementing a better solution for variadic functions.

@mhegazy
Copy link
Contributor

mhegazy commented Oct 17, 2017

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy mhegazy closed this as completed Oct 17, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants