Skip to content

Multiple rest arguments may be allowed if they occupy different tuple nexting levels.Β #57182

Closed as not planned

Description

πŸ”Ž Search Terms

Multiple rest arguments may be allowed in tuples

πŸ•— Version & Regression Information

At least >=4.3.5

⏯ Playground Link

play

πŸ’» Code

declare const f: (x: number, ...args: [number, ...[...string[],boolean,...[boolean,...string[]]]])=>void;
declare const g: (x: number, ...args: [number, ...[...string[],([boolean]|[boolean,boolean]),...string[]]])=>void;
//                                                                                             ~~~~~~~~~~~
// A rest element cannot follow another rest element.(1265)

f; //f(x: number, args_0: number, ...args_1: (string | boolean)[]): void

f(1,1,false);
f(1,1,false,false);
f(1,1,false,"");
f(1,1,false,false,"");
f(1,1,"",false,"");
f(1,1,"",false,false,"");

f(1,1,"",false,"",false,"",false);


declare const t: [x: number, ...args: [number, ...[...string[],boolean,...[boolean,...string[]]]]];
declare const u: [x: number, ...args: [number, ...[...string[],([boolean]|[boolean,boolean]),...string[]]]];
//                                                                                           ~~~~~~~~
// A rest element cannot follow another rest element.(1265)

t; // const t: [x: number, number, ...(string | boolean)[]]

πŸ™ Actual behavior

f, t pass

πŸ™‚ Expected behavior

f and t are equivalent to g and u respectively, so perhaps should not pass.

Additional information about the issue

No response

Activity

  1. RyanCavanaugh commented on Feb 1, 2024

    @RyanCavanaugh
    Member

    The rule is just here to inform you that the thing you think syntactically is happening might not be. It can be evaded if you try hard enough (more simply, through aliasing/instantiation).

  2. craigphicks commented on Feb 2, 2024

    @craigphicks
    Author

    Ryan Cavanaugh (@RyanCavanaugh) - Yes, I get that it is more of warning than an error. The point of this issue is that both f and g contain double rest elements, but only g gets the error warning.
    Because it is actually just a warning and not really an error, I would agree that it is not a defect, in both cases. If that's what you mean, then I agree.

  3. typescript-bot commented on Feb 5, 2024

    @typescript-bot
    Contributor

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

  4. unicornware commented on Mar 14, 2024

    @unicornware

    The rule is just here to inform you that the thing you think syntactically is happening might not be. It can be evaded if you try hard enough (more simply, through aliasing/instantiation).

    Ryan Cavanaugh (@RyanCavanaugh) do you have examples of this? is there a way to spread an array between elements of a tuple?

    type Comment = { type: 'comment' }
    type Identifier = { type: 'identifier' }
    type Nothing = { type: 'nothing' }
    type StringLiteral = { type: 'string' }
    
    // type Comments = Comment[]
    type Comments = [] | [Comment, ...Comment[]]
    
    type Children = [
      ...Comments,
      Identifier | Nothing,
      ...Comments,
      StringLiteral
    ]
    
    // | [Identifier | Nothing, StringLiteral] 
    // | [Identifier | Nothing, Comment, ...Comment[], StringLiteral]
    // | [Comment, ...Comment[], Identifier | Nothing, StringLiteral] 
    // | [Comment, ...(Comment | Identifier | Nothing)[], StringLiteral]

    in the example above, the last tuple in the resulting union allows for multiple Identifier or Nothing types, when it should only allow one. expressed with invalid syntax, something like: [Comment, ...Comment[], Identifier | Nothing, Comment, ...Comment[], StringLiteral].

  5. craigphicks commented on Mar 15, 2024

    @craigphicks
    Author

    Lex (@unicornware) - I had assumed that the internal representation was limited to the following:

    • From the left:
    [ <union of types that could be in 0th place>, <union of types that could be in 1th place>, .... ]
    

    and that eventually reaches the end of the tuple, or a final repeating element.

    • From the right: (Same as from from the left but starting at the end.)

    If that representation were actually the case, then I wouldn't be surprised by multiple Identifier | Nothing, just because it couldn't know for sure at which index that Identifier | Nothing would appear. (Of course a different internal representation could allow "single instance" along with "unknown index", but that would count as a new feature).

    Given that assumed representation we would get:
    From the left: [...(Comment|Identifier|Nothing|StringLiteral)[]] Note: it is equivalent to an array
    From the right: [StringLiteral, ...(Comment|Identifier|Nothing)[]]
    Unified, left first: [...(Comment|Identifier|Nothing)[], StringLiteral]

    However, the type shown by TypeScript user interface:

    // | [Identifier | Nothing, StringLiteral] 
    // | [Identifier | Nothing, Comment, ...Comment[], StringLiteral]
    // | [Comment, ...Comment[], Identifier | Nothing, StringLiteral] 
    // | [Comment, ...(Comment | Identifier | Nothing)[], StringLiteral]
    

    obviously don't follow that assumption.

    Yet the behavior you observed does follow that assumption.

    It seems that the type displayed via tsserver and the type effectively being used by the compiler (which you observed) diverge, at some point.

  6. added a commit that references this issue on Mar 15, 2024
  7. 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