You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Function overload requires an if statement for TS to parse argument types correctlyย #54027
// Say a function myFunc takes in 2 arguments. // Both arguments are either strings or booleans.typeMyFunc=|[arg1: string,arg2: string]|[arg1: boolean,arg2: boolean];constmyFunc=(...[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 & booleanconstmyFunc2=(...[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 invokedconstmyFunc3=(...[arg1,arg2]: MyFunc)=>{if(typeofarg1==='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.
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:
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.
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
typeMyFuncObjArgs=|{arg1: string;arg2: string;}|{arg1: boolean;arg2: boolean};constmyFuncObj=(args: MyFuncObjArgs)=>{};typeMyFuncArrayArgs=|[arg1: string,arg2: string]|[arg1: boolean,arg2: boolean];constmyFunc=(...[arg1,arg2]: MyFuncArrayArgs)=>{// Have to destructure here to create an object, but Typescript yellsmyFuncObj({ arg1, arg2 });};
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.
Activity
MartinJohns commentedon Apr 26, 2023
Duplicate of #40827.
nmain commentedon Apr 26, 2023
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
arg1andarg2. You can get around the limitation here by not destructuring:fatcerberus commentedon Apr 26, 2023
See also #30581 and/or #35873
xitanggg commentedon Apr 27, 2023
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
MyFuncasMyFuncArgsfatcerberus commentedon Apr 27, 2023
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
MyFuncin 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
MyFuncaround, which is known upfront to be a valid input to the call.xitanggg commentedon Apr 27, 2023
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
Playground
fatcerberus commentedon Apr 27, 2023
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.