25

I am trying to use the spread-operator on a typescript-function call like this:

function foo(x: number, y: number, z: number) {
  console.log(x + y + z);
}
const args = [0, 1, 2];
foo(...args);

But on compilation, I get the error: "A spread argument must either have a tuple type or be passed to a rest parameter" (TS2556). What am I doing wrong?

Addendum: How can I approach the problem when my argument is a dynamic array, as in

const args = new Array(3).map(() => Math.random());

5 Answers 5

30

Edit for dynamically generated args:

Option one: use type assertion if you are sure args will always be 3 elements tuple.

const args = new Array(3).map(() => Math.random()) as [number, number, number];

Option two, define foo to accept rest parameter:

function foo(...args: number[]) {
  console.log(args[0] + args[1] + args[2]);
}
const args = new Array(3).map(() => Math.random());
foo(...args);

Old answer for predefined args

You can assert args as const:

const args = [0, 1, 2] as const;

Playground

Or define args as tuple as the error suggested:

const args: [number, number, number] = [0, 1, 2];

Playground

This is to guarantee that number / type of elements in args always match what's required by the function parameters.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! Unfortunately, it turned out that my minimal example was too minimal - my initialization of the args array is more complex -- I it is truly an array (see edit). But your explanation already helps me understanding the underlying issue -- I guess, since Arrays are mutable, there is no easy way to statically guarantee that the array-size matches, right?
Yeah if you can't guarantee the input size of args then you need to define function parameters to be rest type so it also accepts indefinite number of parameters.
6

You can use const assertion to get rid of the problem. Because the function foo expects exactly 3 parameters:

function foo(x: number, y: number, z: number) {
  console.log(x + y + z);
}

const args = [0, 1, 2] as const;
foo(...args);

Comments

1

For dynamically inferred param sizes, the as const method doesn't work.

Another solution is to use the array resulting from ...args in an .apply() call. Typescript doesn't complain about this:

export class CustomError<
  ErrorCode extends keyof typeof CustomError.codes,
  ErrorParams extends Parameters<typeof CustomError.codes[ErrorCode]>
> extends Error {
  constructor(public code: ErrorCode, ...args: ErrorParams) {
    super(CustomError.codes[code].apply(null, args))
  }

  static codes = {
    invalidEmail (email: string) {
      return `Email ${email} is invalid`
    },
    invalidPasswordConfirm: (a: string, b: string) {
      return `Password ${a} and confirm ${b} are different`
    }
  }
}

new CustomError('invalidEmail', 'aaa@bbb')
// ^ CustomError(code: "invalidEmail", email: string)
new CustomError('invalidPasswordConfirm', 'first', 'second')
// ^ CustomError(code: "invalidPasswordConfirm", a: string, b: string)

1 Comment

I believe your answer only works because .apply(thisArg, args) is not strongly typed, for historic reasons, unless you use the strictBindCallApply option.
-2

Arguments don’t work in that way, this is how you should use them:

function multiply(n, ...m) {
    return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);

Comments

-4

Whoever suffers from A spread argument must either have a tuple type or be passed to a rest parameter., you might want to check whether typescript-parser is install in your devDependencies.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.