Skip to content

Type inference of 2 or more generic types from rest parameters being mapped type #47497

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
Igorbek opened this issue Jan 18, 2022 · 3 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@Igorbek
Copy link
Contributor

Igorbek commented Jan 18, 2022

Bug Report

🔎 Search Terms

  • infer multiple generic types from rest parameters being mapped type

🕗 Version & Regression Information

  • This is the behavior in every version I tried (4.3~4.4, 4.6 nightly), and I reviewed the FAQ for entries about type inference

⏯ Playground Link

Playground link with relevant code

💻 Code

// some generic type with 2 type parameters
type F<I, O> = { __type: (input: I) => O }

// this is what I want
declare function join<I, OS extends unknown[]>(...fs: { [K in keyof OS]: F<I, OS[K]> }): F<I, OS>

declare const f1: F<number, string>
declare const f2: F<number, boolean>

// (1)
// expected: join<number, [string, boolean, string]>
join(f1, f2, f1) // actual: join<unknown, [string, boolean, string]>, I type is inferred as unknown
//   ~~ Argument of type 'F<number, string>' is not assignable to parameter of type 'F<unknown, string>'.

// let's try with fixed tuple
declare function join2<I, OS extends readonly [unknown, unknown, unknown]>(...fs: [F<I, OS[0]>, F<I, OS[1]>, F<I, OS[2]>]): F<I, OS>

// (2)
// expected: join2<number, [string, boolean, string]>
join2(f1, f2, f1) // actual: join2<number, [unknown, unknown, unknown]>, I inferred correctly, but OS isn't

// not what I need, but let's try with only inferring from mapped type
declare function join3<OS extends unknown[]>(...fs: { [K in keyof OS]: F<number, OS[K]> }): F<number, OS>

// expected: join3<[string, boolean, string]>
join3(f1, f2, f1) // actual, as expected: join3<[string, boolean, string]>

🙁 Actual behavior

  • (1) call to be inferred as join<unknown, [string, boolean, string]>, could not infer I
  • (2) call to be inferred as join2<number, [unknown, unknown, unknown]>, could not infer OS

🙂 Expected behavior

  • (1) call to be inferred as join<number, [string, boolean, string]>
  • (2) call to be inferred as join2<number, [string, boolean, string]>
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jan 20, 2022

Possibly (???) related to #47226 or #46741 (????)

I got this to work:

type ReturnsOf<T> = { [K in keyof T]: T[K] extends F<never, infer R> ? R : "sentinel nope" };
declare function join4<I, FS extends readonly (F<I, unknown>)[]>(...fs: FS & F<I, unknown>[]): F<I, ReturnsOf<FS>>;
const r = join4(f1, f2, f1);

Let me know if it fails some use case.

In general I don't think the sort of inference to { [K in keyof OS]: F<I, OS[K]> } is possible with our current machinery; it's almost circular by definition and would require a lot of rethinking.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Jan 20, 2022
@Igorbek
Copy link
Contributor Author

Igorbek commented Jan 20, 2022

I see, it can be worked around. I actually was able to come up with the same solution. However, I thought it might be worth to point out these inconsistencies and limitations. The original typing of the function seems much more reasonable and surprisingly not working. I take a look at the referenced issues if they are really the same. But I'd say this repro is quite understand anyway.

@tadhgmister
Copy link

if you specify join takes at least one argument it gives a position for typescript to infer I outside the mapped tuple and works much better

declare function join<I, O1, OS extends unknown[]>(f1: F<I,O1>, ...fs: { [K in keyof OS]: F<I, OS[K]> }): F<I, [O1, ...OS]>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants