Skip to content

Overloaded function args not inferred correctly #54539

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
imatlopez opened this issue Jun 5, 2023 · 7 comments
Open

Overloaded function args not inferred correctly #54539

imatlopez opened this issue Jun 5, 2023 · 7 comments
Labels
Help Wanted You can do this Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases
Milestone

Comments

@imatlopez
Copy link

Bug Report

πŸ”Ž Search Terms

overload functions

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about overloading and different function arguments behavior

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

function a1(arg1: unknown): 1;
function a1(arg1: unknown, arg2: unknown): 2;
function a1(...args: ([arg1: unknown] | [arg1: unknown, arg2: unknown])): 1 | 2 {
    return args.length;
}

const b1: typeof a1 = function f1(...args) {
    return a1(...args);
}

even simpler is using an interface

interface a1 {
    (arg1: unknown): 1;
    (arg1: unknown, arg2: unknown): 2;
}

// errors
const b1: a1 = function f1(...args) {
    return args.length;
}

πŸ™ Actual behavior

Error: Target signature provides too few arguments. Expected 2 or more, but got one., given the single signature of b1 above is variadic, and in fact matches the implementation signature of a1, one would assume the two functions are equivalent (from a signature perspective)

πŸ™‚ Expected behavior

The type of args in b1 should be inferred as the union of all overloads of a1 since it is the only way the variadic args would satisfy the assigned typeof a1 requirement.

@RyanCavanaugh
Copy link
Member

The logic that's going wrong here is that we fetch the contextual type to give to ...args based on the LHS argument lists. There we get [unknown], and [unknown unknown], and reduce that to [unknown unknown] since in general a longer list of parameter contextual types can only help you. Here, it hurts you since it makes the args tuple type too long for one of the signatures.

function a1(arg1: unknown): 1;
function a1(arg1: unknown, arg2: unknown): 2;
function a1(...args: ([arg1: unknown] | [arg1: unknown, arg2: unknown])): 1 | 2 {
    return args.length;
}

const b1: typeof a1 = function f1(...args) {
    return a1(...args);
}

const b2: typeof a1 = function f1(arg1, arg2?) {
    return 1;
}

I believe we just need to special case rest args to handle their contextual type a little bit differently

@RyanCavanaugh RyanCavanaugh added the Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases label Jun 5, 2023
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jun 5, 2023
@RyanCavanaugh RyanCavanaugh added the Help Wanted You can do this label Jun 5, 2023
@glekner
Copy link

glekner commented Jun 25, 2023

Happens for me aswell

@imatlopez
Copy link
Author

Weirdly enough, the reported above is from the playground but locally the same code with Node 18 and TS 5.1 will report

test.ts:7:7 - error TS2322: Type '(...args: any[]) => number' is not assignable to type 'a1'.
  Type 'number' is not assignable to type '1'.

7 const b1: a1 = function f1(...args) {
        ~~

@Kerrick
Copy link

Kerrick commented Aug 24, 2023

In the mean time, is there a workaround to get this kind of overloading functionality without triggering this bug?

@ackvf
Copy link

ackvf commented Sep 19, 2023

Minimal reproduction typescriptlang.org/play?#

interface Options {}

interface Props {
  action: {
    (): void
    (newOptions: Partial<Options>): Options
  }
}

declare const x: Props
const a = x.action({}) // correctly: Options
const b = x.action()   // correctly: void

x.action = (newOptions) => { // Error: Target signature provides too few arguments. Expected 1 or more, but got 0.(2322)
  if (newOptions) {
    return newOptions
  }
  return
}

Since the type inference works properly outside of the implementation, I guess I will resort to this in the meantime.

// @ts-expect-error Target signature provides too few arguments. Expected 1 or more, but got 0.
x.action = (newOptions) => {
  ...

@LeonardDrs
Copy link

LeonardDrs commented Jun 6, 2024

Hi,
Workaround for this is to have always the same number of arguments, but typing unneeded one as optional never.

interface a1 {
	(arg1: unknown, arg2?: never): 1;
	(arg1: unknown, arg2: unknown): 2;
}

Note: In the example, we still have an issue: Type '2' is not assignable to type '1'. This workaround only works if your function always return the same type.

@yuqi-foxtel
Copy link

yuqi-foxtel commented Jun 27, 2024

May I know how b is inferred as unknown instead of boolean?

function xx(a: string): void;
function xx(a: string, b: boolean, c?: string): void;
function xx(a: string, b = true, c?: string): void {
    console.log(a, b, c)
 }

const xxx: typeof xx = function xxx(a: string, b = true, c?: string): void { // b is unknown here
    xx(a, b, c); // Argument of type 'unknown' is not assignable to parameter of type 'boolean'.
};

Playground link

And if I turn off noImplicitAny, b can be inferred as boolean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Help Wanted You can do this Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases
Projects
None yet
Development

No branches or pull requests

7 participants