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

Generics get broken when inferred from a function with an implicitly typed parameter #54438

Closed
mattersj opened this issue May 29, 2023 · 3 comments

Comments

@mattersj
Copy link

Bug Report

πŸ”Ž Search Terms

generic, function, parameters, unknown, never

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

interface Entity {
    id: number;
    name: string;
}

type Fn<T> = (params: Record<string, unknown>) => T[];

declare function test<T, K extends keyof T>(fn: Fn<T>, key: K): T;

// Case #1: params aren't used, generic is `<Entity, 'id'>` βœ…
test(() => [] as Entity[], 'id');

// Case #2: params are used with a type being explicitly specified, generic is `<Entity, 'name'>` βœ…
test((params: Record<string, unknown>) => [] as Entity[], 'name');

// Case #3: params are used with a different type, generic is `<Entity, 'id'>` βœ…
test((params: unknown) => [] as Entity[], 'id');

// Case #4: params are used with an implicit type, generic is <unknown, never> ❌
test((params) => [] as Entity[], 'id');
      ^ but it's inferred properly as Record<string, unknown>

πŸ™ Actual behavior

In the case 4 a generic gets broken and has a type <unknown, never> instead of <Entity, 'id'> as shown in the previous examples.
There's nothing wrong with an implicit type for params since it's already automatically inferred as Record<string, unknown>.

πŸ™‚ Expected behavior

The case 4 should work exactly the same as the previous ones and missing type annotation in parameters should have no impact on the inferred generic type.

@jcalz
Copy link
Contributor

jcalz commented May 29, 2023

Duplicates the part of #47599 that's still unresolved... and might never be resolved, see #48538 (comment):

() => 42 and (n: number) => n are context insensitive because they have no contextually typed parameters, but n => n and function() { return 42 } are context sensitive because they have at least one contextually typed parameter (in the function expression case, the implicit this parameter is contextually typed). The errors in the last two calls occur because inferred type information only flows from left to right between context sensitive arguments. This is a long standing limitation of our inference algorithm, and one that isn't likely to change. [emphasis added]

@fatcerberus
Copy link

fatcerberus commented May 29, 2023

Thanks @jcalz, I suspected this was related to contextual typing but wasn't confident enough about the cause to jump in. I thought it might just be a circularity (i.e. T infers from function parameter -> arrow function is contextually typed by T -> cycle detected -> default to constraint)

@mattersj
Copy link
Author

@jcalz Thank you for clearing this up, it makes sense now. I'm going to close this one as it already has a bunch of duplicates out there.
So, for anyone coming up here the best solution is to explicitly add a type annotation for your parameter like this: (params: YourType) => ... or get rid of your parameter at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants