microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.76k stars 12.46k forks source link

High-order functions don't respect generic input type #52010

Closed leobastiani closed 8 months ago

leobastiani commented 1 year ago

Bug Report

🔎 Search Terms

generic type inference, higher-order functions

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about generic type inference and higher-order functions.

⏯ Playground Link

Workbench Repro

💻 Code

type MyProps = { a: number };

type AddNumberHof = <Props extends {}>(props: Props) => Props & { b: number };

type ApplyToHof<
  Hof extends (props: any) => any,
  Props
> = Hof extends (props: Props) => infer R ? R : never;

type MyPropsWithB = ApplyToHof<AddNumberHof, MyProps>;

const example: MyPropsWithB = {
  a: 1,
  b: 2
};

🙁 Actual behavior

Error at last line Type '{ a: number; b: number; }' is not assignable to type '{ b: number; }'. Object literal may only specify known properties, and 'a' does not exist in type '{ b: number; }'.

🙂 Expected behavior

Build successfully

leobastiani commented 1 year ago

This code below should give an error

type MyProps = { a: number };

type IdentityHof = <Props extends {}>(props: Props) => Props;

type ApplyToHof<
  Hof extends (props: Props) => any,
  Props extends {}
> = Hof extends (props: Props) => infer R ? R : never;

type SameAsMyProps = ApplyToHof<IdentityHof, MyProps>;

const example: SameAsMyProps = {
  c: 1
};

Workbench Repro

RyanCavanaugh commented 1 year ago

Structural inference, which is what's happening here, doesn't perform generic instantiation like would be needed to resolve this. The performance implications of doing that would be, as far as I can imagine, catastrophic.

leobastiani commented 1 year ago

Ideally, I'd like to be able to transform type IdentityHof = <Props extends {}>(props: Props) => Props; to type IdentityHofTwo<Props extends {}> = (props: Props) => Props;. Is it possible?

leobastiani commented 1 year ago

The main issue here is getting the return of a function like () => <Props extends {}>(props: Props) => Props & { b: number; } from an input like { a: number; } and finally getting the result { a: number; b: number; }.

Although applying ReturnType to the high-order function in this example returns <Props extends {}>(props: Props) => Props & { b: number; }