Skip to content

Optional properties on function interface breaks inference of type parameters #52262

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

Open
zach-waggoner opened this issue Jan 16, 2023 · 2 comments · May be fixed by #52495
Open

Optional properties on function interface breaks inference of type parameters #52262

zach-waggoner opened this issue Jan 16, 2023 · 2 comments · May be fixed by #52495
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@zach-waggoner
Copy link

zach-waggoner commented Jan 16, 2023

Bug Report

🔎 Search Terms

function interface optional properties generics type parameters inference

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about type inference.

⏯ Playground Link

Playground link with relevant code

💻 Code

interface FuncA<T> {
  (arg: T): void;
};

interface FuncB<T> {
  (arg: T): void;
  x?: string;
};

const outerA = <T,>(func: FuncA<T>, arg: T) => {};
const outerB = <T,>(func: FuncB<T>, arg: T) => {};
const inner = <T extends 'a' | 'b' | 'c'>(cb: (arg: T) => void) => {};

outerA(inner, (arg: 'a' | 'b') => {});
outerB(inner, (arg: 'a' | 'b') => {}); // error

🙁 Actual behavior

In the call to outerA(), the type of the func parameter is inferred as FuncA<(arg: 'a' | 'b') => void>, as determined by the type of the arg parameter.

In the call to outerB(), the type of the func parameter is inferred instead as FuncB<(arg: 'a' | 'b' | 'c') => void>, using the base constraint of inner's T parameter rather than the subtype of that constraint used by the arg parameter.

🙂 Expected behavior

I do not expect the addition of an optional property on FuncB<T> to break the type parameter inference. It seems that inner's T parameter should be inferred as 'a' | 'b' in both cases.

@zach-waggoner
Copy link
Author

zach-waggoner commented Jan 17, 2023

For some context, this problem comes up in React if you have a component that accepts a generic component and its props:

interface OuterProps<P> {
  component: FunctionComponent<P>;
  props: P;
}

interface InnerProps<T extends 'a' | 'b' | 'c'> {
  cb: (arg: T) => void;
}

const Outer = <P,>(props: PropsWithChildren<OuterProps<P>>) => null;
const Inner = <T extends 'a' | 'b' | 'c'>(props: PropsWithChildren<InnerProps<T>>) => null;

<Outer component={Inner} props={{ cb: (arg: 'a' | 'b') => {} }} />; // error

This fails with:

TS2322: Type '(arg: 'a' | 'b') => void' is not assignable to type '(arg: "a" | "b" | "c") => void'.
  Types of parameters 'arg' and 'arg' are incompatible.
    Type '"a" | "b" | "c"' is not assignable to type '"a" | "b"'.
      Type '"c"' is not assignable to type '"a" | "b"'.

The error goes away if you change FunctionComponent<P> to (props: PropsWithChildren<P>, context?: any) => ReactElement<any, any> | null (just the call signature without the optional properties).

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

I have an open PR that fixes the OP's problem here. However, it doesn't really address the true motivation of the OP:

this problem comes up in React if you have a component that accepts a generic component and its props

For context, see this comment

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

Successfully merging a pull request may close this issue.

3 participants