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
Generic type of varargs is inferred from only the first parameter #51273
typeA={a: any};typeB={b: any};declareconsta: A;declareconstb: B;functiontakeVarArgsOfGeneric<ARG>(...args: Array<ARG>){console.log(args);}// error, infers ARG from only the first positiontakeVarArgsOfGeneric(a,b);// but ok when spreadtakeVarArgsOfGeneric(...[a,b]);
🙁 Actual behavior
Type error:
Argument of type 'B' is not assignable to parameter of type 'A'.
Property 'a' is missing in type 'B' but required in type 'A'.
Discussion in #37673 concludes that this is working as intended. Argument being that it is the same behaviour as a non-varargs variant.
// same issue when positionedfunctiontakeMultipleGeneric<ARG>(first: ARG,second: ARG){console.log(...arguments);}takeMultipleGeneric(a,b);
But doesn't go further into explaining why this would be the desired behaviour, if there is one it should make it into the FAQ.
Here I suggest it is not the desired behaviour both in the varags/rest and the multiple positions cases.
This is not exactly the same case though. When using the rest syntax, the programmer gets to explicitly name and type the rest parameter as an Array of something and would expect the type system to resolve types in the same way arrays are resolved. I.e.
In the case of positioned parameters, this would be expecting typescript implicitly resolves the type of arguments which by specification is truly dynamic and rightfully have type being the equivalence of ArrayLike<any>.
It should, however, be expected that the named positional parameters types are resolved as unions.
Inference collects some number of candidates and then decides what to do with those candidates. In the case where 2 or more candidates are collected but no common type is found, it falls back to one of them and tries to process the call anyway. In the case where 1 candidate is collected, that's obviously the type argument's value.
Automatically inferring a union with two different types are present means that generic inference can effectively never fail; this was effectively the behavior prior to TS 1.6 and we got bugs basically every day saying that this makes generics useless. A thought experiment of "What calls would be allowed if this were the case" quickly reveals how undesirable this would be as a default behavior.
So the question is whether a spreaded array should act like the code that collects 2 arguments or 1, and you can make a consistency-based argument in either direction:
// Direct call: Collects two candidates (A and B).// Union is not automatically inferred from two disparate candidates (intended)takeVarArgsOfGeneric(a,b);// At issue: middle case behaves as the one above, or the one below?takeVarArgsOfGeneric(...[a,b]);// Indirected type: (A | B)[]constarr=[a,b];// Only one candidate is collected (A | B)takeVarArgsOfGeneric(...arr);// is same astakeVarArgsOfGeneric<A|B>(...arr);
classValueContainer<T>{publicconstructor(publicvalue:T){}}declareconsta: ValueContainer<number>;declareconstb: ValueContainer<string>;functiontakeVarArgsOfGeneric<ARG>(...args: Array<ValueContainer<ARG>>){returnargs[0].value;}// error: ValueContainer<string> is not assignable to ValueContainer<number>varresult1=takeVarArgsOfGeneric(a,b);// result 1 expected to be number | string// error: ValueContainer<number> | ValueContainer<string> is not assignable to ValueContainer<number>varresult2=takeVarArgsOfGeneric(...[a,b]);// result 2 expected to be number | string
Ryan Cavanaugh (@RyanCavanaugh) Is it somehow possible to solve that by giving Typescript more information about the generic structure?
Activity
MartinJohns commentedon Oct 22, 2022
Pretty sure it's a duplicate of #37673.
gomain commentedon Oct 24, 2022
Discussion in #37673 concludes that this is working as intended. Argument being that it is the same behaviour as a non-varargs variant.
But doesn't go further into explaining why this would be the desired behaviour, if there is one it should make it into the FAQ.
Here I suggest it is not the desired behaviour both in the varags/rest and the multiple positions cases.
This is not exactly the same case though. When using the rest syntax, the programmer gets to explicitly name and type the rest parameter as an
Arrayof something and would expect the type system to resolve types in the same way arrays are resolved. I.e.In the case of positioned parameters, this would be expecting typescript implicitly resolves the type of
argumentswhich by specification is truly dynamic and rightfully have type being the equivalence ofArrayLike<any>.It should, however, be expected that the named positional parameters types are resolved as unions.
Is this a case of lacking unification #30134?
RyanCavanaugh commentedon Oct 25, 2022
Inference collects some number of candidates and then decides what to do with those candidates. In the case where 2 or more candidates are collected but no common type is found, it falls back to one of them and tries to process the call anyway. In the case where 1 candidate is collected, that's obviously the type argument's value.
Automatically inferring a union with two different types are present means that generic inference can effectively never fail; this was effectively the behavior prior to TS 1.6 and we got bugs basically every day saying that this makes generics useless. A thought experiment of "What calls would be allowed if this were the case" quickly reveals how undesirable this would be as a default behavior.
So the question is whether a spreaded array should act like the code that collects 2 arguments or 1, and you can make a consistency-based argument in either direction:
This isn't really frequently asked FWIW
gomain commentedon Oct 26, 2022
Ryan Cavanaugh (@RyanCavanaugh) Thanks for explaining.
To recap. When you declare a function as
you want the type system to enforce that
firstandsecondare indeed the same type, and fail otherwise. Had they resolved as unions, things likewould be useless.
On the other hand, if you declare
you get same type enforcement when called using positioned variant (multiple candidates provided).
but don't when called using the spread variant as the type of the array (the single candidate) is resolved prior to resolving
T.TFTomSun commentedon Jan 21, 2023
I have a similar use case, I try to infer a generic argument from an generic container array.`Both discussed ways don't work in that case.
TS Playground
Ryan Cavanaugh (@RyanCavanaugh) Is it somehow possible to solve that by giving Typescript more information about the generic structure?
RyanCavanaugh commentedon Jan 24, 2023
Yes
github-actions commentedon Jun 8, 2023
This issue has been marked as 'Not a Defect' and has seen no recent activity. It has been automatically closed for house-keeping purposes.