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

Function overload requires an if statement for TS to parse argument types correctly #54027

Closed
xitanggg opened this issue Apr 26, 2023 · 7 comments
Labels
Duplicate An existing issue was already created

Comments

@xitanggg
Copy link

🔎 Search Terms

Function overload

🕗 Version & Regression Information

This is the behavior in every version I tried starting at 4.x, as well as the nightly version

⏯ Playground Link

Playground link with relevant code

💻 Code

// Say a function myFunc takes in 2 arguments. 
// Both arguments are either strings or booleans.
type MyFunc =
  | [arg1: string, arg2: string]
  | [arg1: boolean, arg2: boolean];
const myFunc = (...[arg1, arg2]: MyFunc) => {}

// myFunc2 has the same input type and invokes myFunc inside
// TS throws an error because it thinks arg1/arg2 can be both string & boolean
const myFunc2 = (...[arg1, arg2]: MyFunc) => {
  myFunc(arg1, arg2)
}

// myFunc3 has the same input type and invokes myFunc inside
// TS doesn't throw an error because myFunc3 has an if statement to separate out string/boolean
// but the if statement is effectively useless since myFunc(arg1, arg2) is always invoked
const myFunc3 = (...[arg1, arg2]: MyFunc) => {
  if (typeof arg1 === 'string'){
    myFunc(arg1, arg2)
  }else{
    myFunc(arg1, arg2)
  }
}

🙁 Actual behavior

TS throws an error for myFunc2 because it thinks arg1 can be both string or boolean but it should only be either one.

🙂 Expected behavior

TS should not throw an error for myFunc2

@MartinJohns
Copy link
Contributor

Duplicate of #40827.

@nmain
Copy link

nmain commented Apr 26, 2023

Duplicate of #40827.

This is a single call signature with a union type for the arguments so it's not actually an overload. This is closer to #32639; the type system can't form a dependent narrowing relationship between arg1 and arg2. You can get around the limitation here by not destructuring:

const myFunc4 = (...args: MyFunc) => {
  myFunc(...args);
}

@fatcerberus
Copy link

See also #30581 and/or #35873

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Apr 26, 2023
@xitanggg
Copy link
Author

xitanggg commented Apr 27, 2023

Thanks all for the pointers and @nmain for the cool workaround, very interesting that not destructuring would make it work 👍

nit: I should have named MyFunc as MyFuncArgs

@fatcerberus
Copy link

@xitanggg Basically the destructured version fails because the compiler is only looking at the types of values individually, it doesn’t track “where they came from” (a MyFunc in this case), so as far as TS is concerned (prior to narrowing) you’re passing in a newly minted [ string | boolean, string | boolean ]—which isn’t allowed by the call signature because it doesn’t guarantee the elements match.

By not destructuring, you keep a MyFunc around, which is known upfront to be a valid input to the call.

@xitanggg
Copy link
Author

xitanggg commented Apr 27, 2023

Thanks for the explanation @fatcerberus. Right, this seems like a TS limitation as it doesn't try to loop through the options of the defined call signature.

My actual use case is slightly more complex with a function that has a call signature of an array union calling another function that has a call signature of an object union. It seems it would have to destructure to create the object with no easy workaround

type MyFuncObjArgs =
  | {
      arg1: string;
      arg2: string;
    }
  | { arg1: boolean; arg2: boolean };
const myFuncObj = (args: MyFuncObjArgs) => {};

type MyFuncArrayArgs =
  | [arg1: string, arg2: string]
  | [arg1: boolean, arg2: boolean];
const myFunc = (...[arg1, arg2]: MyFuncArrayArgs) => {
  // Have to destructure here to create an object, but Typescript yells
  myFuncObj({ arg1, arg2 });
};

Playground

@fatcerberus
Copy link

Right, this seems like a TS limitation as it doesn't try to loop through the options of call signature.

@RyanCavanaugh talks about that often - in general, if something can only be proven safe through manual enumeration of the possibilities, the compiler is most likely going to reject it. Type safety has to be provable “in the abstract”, so to speak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants