Skip to content

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

Closed

Description

@xitanggg

๐Ÿ”Ž 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

Activity

  1. MartinJohns commented on Apr 26, 2023

    @MartinJohns
    Contributor

    Duplicate of #40827.

  2. nmain commented on Apr 26, 2023

    @nmain

    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);
    }
  3. fatcerberus commented on Apr 26, 2023

    @fatcerberus

    See also #30581 and/or #35873

  4. xitanggg commented on Apr 27, 2023

    @xitanggg
    Author

    Thanks all for the pointers and Nicholai Main (@nmain) for the cool workaround, very interesting that not destructuring would make it work ๐Ÿ‘

    nit: I should have named MyFunc as MyFuncArgs

  5. fatcerberus commented on Apr 27, 2023

    @fatcerberus

    Xitang Zhao (@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.

  6. xitanggg commented on Apr 27, 2023

    @xitanggg
    Author

    Thanks for the explanation Bruce Pascoe (@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

  7. fatcerberus commented on Apr 27, 2023

    @fatcerberus

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

    Ryan Cavanaugh (@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.

  8. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions