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

Parameters returns never when used with a generic function that combines Pick and Partial on the generic type argument #61516

Open
12joan opened this issue Apr 1, 2025 · 2 comments
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@12joan
Copy link

12joan commented Apr 1, 2025

πŸ”Ž Search Terms

partial pick generic parameters never

πŸ•— Version & Regression Information

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

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.9.0-dev.20250331#code/C4TwDgpgBA8gRgKygXgFBSgHygbyqSALigHIBDEgGigEsATYgZ2ACcaA7Ac2rKdY85QAvuiy584CMRJwqtBlGZsu1OMXYBXALZwILYQG5UqDsD0AzMgGNoAQQAKASVyiA7gHsWAa0YAeACpQEAAeZux0jLCIAHwAFKIY7ojE-pSiAJTEAG7u9EaiZAA2jO4A6p4+AUGhEOGR8AhxCVBJCMT2NFZeAdQkBBAkYiT0JNFpGJlQOXnGGEUltsVlFX6BIWERUY3xGInJUPZkLMA0RQHRGdm5dPkYdO4QjOzA5d5V67WbDU27LfsdXR6pH6g2wwzooygADIDkcTmd-BcJlcZiJUP0oK8fIcWGQtJFkLDcVoIGYWH4HI4ANokDzeRgkAC60QMvzZ7IwAHpOVAqa1iA1GejJFBFiUsYwcXiCUS8aS9BSnDT5st6UyWRyuTy+f9Ot0GtQAET9Q1iQ30Q3RIUYsXuW0SqX4lCyklkxXU8hLe0rdWs7m8-lE+GFXzfa0igAiDyeLwqjpljvl5N8lJp90ezyxvo5-vYECyeiAA

πŸ’» Code

type Obj =
  | { type: 'a', id: string, a: string }
  | { type: 'b', id: string, b: number };

interface API {
  works<T extends Obj>(
    obj: T,
  ): void;

  alsoWorks<T extends Obj>(
    obj: Pick<T, 'type' | 'id'>,
  ): void;

  alsoAlsoWorks<T extends Obj>(
    obj: Partial<T>
  ): void;

  doesntWork<T extends Obj>(
    obj: Pick<T, 'type' | 'id'> & Partial<T>
  ): void;
}

type WorksParams = Parameters<API['works']>;                 // [obj: Obj]
type AlsoWorksParams = Parameters<API['alsoWorks']>;         // [obj: Pick<Obj, "type" | "id">]
type AlsoAlsoWorksParams = Parameters<API['alsoAlsoWorks']>; // [obj: Partial<Obj>]
type DoesntWorkParams = Parameters<API['doesntWork']>;       // never

πŸ™ Actual behavior

Parameters<API['doesntWork']> returns never

πŸ™‚ Expected behavior

The Parameters<API['doesntWork']> case behaves like the other cases and returns [obj: Pick<Obj, "type" | "id"> & Partial<Obj>].

Additional information about the issue

I'm not sure why Parameters works for all cases except when Pick and Partial are used together. I'm having the same problem when using extends and infer to get the parameters.

My use case is to prepend an argument event: IpcMainInvokeEvent to several generic functions to match the signature expected by the listener argument of Electron's ipcMain.handle method. If anyone knows of a workaround for this Parameters problem, I'd love to hear it.

@RyanCavanaugh
Copy link
Member

Not sure why this is happening. A workaround:

type Obj =
  | { type: 'a', id: string, a: string }
  | { type: 'b', id: string, b: number };

type PartialPick<T, K extends keyof T> = Pick<T, K> & Partial<T>;

interface API {
  works<T extends Obj>(
    obj: T,
  ): void;

  alsoWorks<T extends Obj>(
    obj: Pick<T, 'type' | 'id'>,
  ): void;

  alsoAlsoWorks<T extends Obj>(
    obj: Partial<T>
  ): void;

  doesntWork<T extends Obj>(
    obj: Pick<T, 'type' | 'id'> & Partial<T>
  ): void;

  worksButWhy<T extends Obj>(
    obj: PartialPick<T, 'type' | 'id'>
  ): void;
}

type WorksParams = Parameters<API['works']>;                 // [obj: Obj]
type AlsoWorksParams = Parameters<API['alsoWorks']>;         // [obj: Pick<Obj, "type" | "id">]
type AlsoAlsoWorksParams = Parameters<API['alsoAlsoWorks']>; // [obj: Partial<Obj>]
type DoesntWorkParams = Parameters<API['doesntWork']>;       // never
//    ^?
type WorksButWhy = Parameters<API['worksButWhy']>;       // OK
//    ^?

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Help Wanted You can do this labels Apr 1, 2025
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Apr 1, 2025
@Andarist
Copy link
Contributor

Andarist commented Apr 2, 2025

It boils down to the inability to converge on a union inference in this case (TS playground):

type Obj =
  | { type: "a"; id: string; a: string }
  | { type: "b"; id: string; b: number };

declare function doesntWork<T extends Obj>(
  obj: Pick<T, "type" | "id"> & Partial<T>,
): void;

declare const obj: Obj;

doesntWork(obj); // error
doesntWork<typeof obj>(obj); // ok

I thought that a distributive conditional type would make it work but it doesn't change anything here:

declare function doesntWork<T extends Obj>(
  obj: T extends any ? Pick<T, "type" | "id"> & Partial<T> : never,
): void;

When it comes to worksButWhy - this case is just able to infer between type arguments of a matching type alias.

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