Skip to content

Generic type of varargs is inferred from only the first parameter #51273

Closed as not planned

Description

Bug Report

🔎 Search Terms

  • varargs
  • generic

🕗 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

type A = { a: any };
type B = { b: any };

declare const a: A;
declare const b: B;

function takeVarArgsOfGeneric<ARG>(...args: Array<ARG>) {
  console.log(args);
}
// error, infers ARG from only the first position
takeVarArgsOfGeneric(a, b);
// but ok when spread
takeVarArgsOfGeneric(...[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'.

Inferred type:

function takeVarArgsOfGeneric<A>(...args: A[]): void

🙂 Expected behavior

Inferred type:

function takeVarArgsOfGeneric<A | B>(...args: (A | B)[]): void

Activity

  1. MartinJohns commented on Oct 22, 2022

    @MartinJohns
    Contributor

    Pretty sure it's a duplicate of #37673.

  2. gomain commented on Oct 24, 2022

    @gomain
    Author

    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 Array of something and would expect the type system to resolve types in the same way arrays are resolved. I.e.

    function takeArrayOfGeneric<ARG>(args: Array<ARG>) { }

    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.

    Is this a case of lacking unification #30134?

  3. RyanCavanaugh commented on Oct 25, 2022

    @RyanCavanaugh
    Member

    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);

    if there is one it should make it into the FAQ.

    This isn't really frequently asked FWIW

  4. gomain commented on Oct 26, 2022

    @gomain
    Author

    Ryan Cavanaugh (@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 first and second are indeed the same type, and fail otherwise. Had they resolved as unions, things like

    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 T.

    takeSomeParametersOfType(...[a, b]); // ok, T is A | B
  5. TFTomSun commented on Jan 21, 2023

    @TFTomSun

    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

    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

    Ryan Cavanaugh (@RyanCavanaugh) Is it somehow possible to solve that by giving Typescript more information about the generic structure?

  6. RyanCavanaugh commented on Jan 24, 2023

    @RyanCavanaugh
    Member

    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> {
  7. github-actions commented on Jun 8, 2023

    @github-actions

    This issue has been marked as 'Not a Defect' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  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

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions