Skip to content

Type inference for rest parameters differs from that for arrays #41048

Closed

Description

@ezsh

TypeScript Version: 4.1.0-dev.20201011

Search Terms: rest parameters, type inference

Expected behavior: All test cases are valid ones.

Actual behavior: cases t3 and t4 are correctly understood by the TS compiler, the other ones result in errors:

Argument of type 'number' is not assignable to parameter of type 'string'.
Argument of type '"b"' is not assignable to parameter of type '"a"'.

Related Issues: #37193

Code

declare function f1<T>(...list: ReadonlyArray<T>): T;
declare function f2<T>(...list: T[]): T;
declare function f3<T>(list: T[]): T;

const s:string = "str";
const ar = ["a", "b", "c", 0];

const t1 = f1(s, "a", "b", 0);
const t2 = f2("a", "b", "c", 0);
const t3 = f3(["a", "b", "c", 0]);
const t4 = f1(...ar);
Output
"use strict";
const s = "str";
const ar = ["a", "b", "c", 0];
const t1 = f1(s, "a", "b", 0);
const t2 = f2("a", "b", "c", 0);
const t3 = f3(["a", "b", "c", 0]);
const t4 = f1(...ar);
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": 2,
    "target": "ES2017",
    "jsx": "React",
    "module": "ESNext"
  }
}

Playground Link: Provided

Activity

  1. RyanCavanaugh commented on Oct 12, 2020

    @RyanCavanaugh
    Member

    These are different syntaxes and thus mean different things; by having different syntax do different things you are then able to express different behavior depending on which you want to happen. Changing either one of these behaviors would be a fairly large breaking change and would need to be justified by something more than "it's different".

  2. ezsh commented on Oct 12, 2020

    @ezsh
    Author

    I don't think it works as intended: the t1 case is failing because TS deduces the T type from the first argument only, while it has to use the whole arguments array. For the t2 case the type of T is inferred as "a", which is obviously useless and wrong.

  3. RyanCavanaugh commented on Oct 12, 2020

    @RyanCavanaugh
    Member

    This is the intended behavior. In t2, inference failed to produce a suitable result, so the call was processed according to the first available candidate, which was "a".

  4. ezsh commented on Oct 12, 2020

    @ezsh
    Author

    I can't agree with you. MDN states: "A function's last parameter can be prefixed with ... which will cause all remaining (user supplied) arguments to be placed within a "standard" JavaScript array."

    But this is not the case with TS: for rest parameters it fails to deduce the same union type as it does for array. I'd expect it to infer T to be string|number for t1 and "a" | "b" | "c" | number for t2, right as it does that for the ar declaration.

  5. ezsh commented on Oct 12, 2020

    @ezsh
    Author

    And in any case, all four examples are valid, if TS can't (or does not want to) infer the correct union type, it should have set T to any instead of signalling errors.

  6. RyanCavanaugh commented on Oct 12, 2020

    @RyanCavanaugh
    Member

    "valid" and "correct" are outcomes decided by the type system, and the type system has intentionally been designed with flagging some of these as errors as not others.

  7. ezsh commented on Oct 12, 2020

    @ezsh
    Author

    The type system is wrong then: semantically rest parameters do not differ from arrays is JS, while TS assign them different behaviour. The t4 case is semantically identical to the t1, but TS handles them differently, which is incorrect.

  8. locked as resolved and limited conversation to collaborators on Oct 21, 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

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions