-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Generic type of varargs is inferred from only the first parameter #51273
Comments
Pretty sure it's a duplicate of #37673. |
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 positioned
function takeMultipleGeneric<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 function takeArrayOfGeneric<ARG>(args: Array<ARG>) { } In the case of positioned parameters, this would be expecting typescript implicitly resolves the type of It should, however, be expected that the named positional parameters types are resolved as unions. Is this a case of lacking unification #30134? |
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)[]
const arr = [a, b];
// Only one candidate is collected (A | B)
takeVarArgsOfGeneric(...arr);
// is same as
takeVarArgsOfGeneric<A | B>(...arr);
This isn't really frequently asked FWIW |
@RyanCavanaugh Thanks for explaining. To recap. When you declare a function as function takeTwoParametersOfType<T>(first: T, second: T) { ... } you want the type system to enforce that function max<T>(first: T, second: T): T { ... } would be useless. On the other hand, if you declare function takeSomeParametersOfType<T>(...params: Array<T>) { ... } you get same type enforcement when called using positioned variant (multiple candidates provided). takeSomeParametersOfType(a, b); // Argument of type 'B' is not assignable to parameter of type 'A' but don't when called using the spread variant as the type of the array (the single candidate) is resolved prior to resolving takeSomeParametersOfType(...[a, b]); // ok, T is A | B |
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. class ValueContainer<T>{
public constructor (public value:T){
}
}
declare const a: ValueContainer<number>;
declare const b: ValueContainer<string>;
function takeVarArgsOfGeneric<ARG>(...args: Array<ValueContainer<ARG>>) {
return args[0].value;
}
// error: ValueContainer<string> is not assignable to ValueContainer<number>
var result1 = takeVarArgsOfGeneric(a, b);
// result 1 expected to be number | string
// error: ValueContainer<number> | ValueContainer<string> is not assignable to ValueContainer<number>
var result2 = takeVarArgsOfGeneric(...[a, b]);
// result 2 expected to be number | string @RyanCavanaugh Is it somehow possible to solve that by giving Typescript more information about the generic structure? |
Yes type Helper1<T> = T extends { [n: number]: infer X } ? Helper2<X> : never;
type Helper2<T> = T extends ValueContainer<infer U> ? U : never;
function takeVarArgsOfGeneric<ARG>(...args: ARG & Array<ValueContainer<any>>): Helper1<ARG> { |
This issue has been marked as 'Not a Defect' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Bug Report
🔎 Search Terms
🕗 Version & Regression Information
This is a crash
No
This changed between versions ______ and _______
All version in the playground error. Versions 3.62 and earlier don't give meaningful error, i.e. just Errors in code.
This is the behavior in every version I tried, and I reviewed the FAQ for entries about ________
Yes. And there is no FAQ on this subject.
I was unable to test this on prior versions because _______
All version in the playground have been tested. Including Nightly.
⏯ Playground Link
Playground link with relevant code
💻 Code
🙁 Actual behavior
Type error:
Inferred type:
🙂 Expected behavior
Inferred type:
The text was updated successfully, but these errors were encountered: