Typescript: Compiler incorrectly reports parameter/call target signature mismatch when using the spread operator

Created on 3 Aug 2015  ·  55Comments  ·  Source: microsoft/TypeScript

The compiler incorrectly reports a parameter/call target signature mismatch when using the spread operator. Pasting this code into the TypeScript Playground:

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

produces this error:

Supplied parameters do not match any signature of call target.
Bug Fixed

Most helpful comment

@jpike88 I know you mean well, but you might not realize statements like these are counter productive. The issue is on their TODO for milestones 2.7 or 2.8 it looks like. It might seem like an obvious thing to fix, but when you have thousands of obvious things to fix you can't possibly fix them all at the same time, and continue to improve other things that are expected of them. The best way to voice your vote is to give the original post at the top a 👍 reaction. This is free software after all and they don't owe us anything. That said, that's just my take, another user
of TS like you 🕺

All 55 comments

And the same is for when args is of any-type

function foo(x: number, y: number, z: number) { }

function bar(...args) {
    foo(...args); // Supplied parameters do not match any signature of call target.
}

this is currently by design. but we should reconsider it.

@smashdevcode, @tjoskar, I'm looking for some real-world uses of this feature. Specifically, do you expect to spread arrays or tuples (or both maybe)? Here are couple of toy examples:

//// arrays ////
var a1: number[] = [1,2];
var a2: number[] = [1,2,3];
var a3: number[] = [];
function twoNumbersOrSo(n?: number, m?: number) {
  return (n || -1) * (m || -1);
}
function howManyNumbersLessOne(n?: number, ...ns: number[]) {
    return ns.length;
}

//// tuples ////
var t1: [number, string] = [1, "foo"];
var t2: [number, string, string] = [1, "foo", "bar"];
function veryTraditional(n: number, s: string) {
    for (let i = 0; i < n; i++) {
        console.log(s);
    }
}
function moreInteresting(n: number, s: string, ...rest: string[]) {
    veryTraditional(n, s);
    console.log(rest);
}

Now any of a1,a2,a3 can be applied to the first two functions, and any of t1,t2 can be applied to the second two.

Notice that with arrays:

  1. Have a single type, so parameters all have to be the same type. (I guess this could be any.)
  2. The length of arrays is not known at compile time, so the parameters have to be optional. And the spread argument has to be the last (like today).

With tuples:

  1. The value will have to have a type annotation at some point. Otherwise it will be interpreted as an array (eg (number | string)[] not [number, number, string]). In toy code this erases any savings in brevity, but it might be OK in a real project that already defines lots of interfaces.
  2. The length is known ahead of time, so a tuple can be applied to any kind of parameter list.

Hi @sandersn,

Sorry for late response. I totally missed your reply.
I only use arrays with one datatype so the tuple-version is not very interesting for me.
However I think that the spread operation in function calls should accepts any iterable object. So it should work with eg. map and set, and in these cases we will need to create an interface that works with the spread operator even if the data contains multiple datatypes (eg. new Set().add(1).add('string')). But that is maybe another ticket?

Example with Set and the spread operation (works in Chrome 46)

function foo(...args) {
    console.log(...args);
}
var bar = new Set().add(1).add(2);

foo(...bar); // 1, 2

Yeah, it's a separate issue. Typescript already supports arrays as the type for rest parameters, which is the thing that would need to be made generic. We're just discussing where spread args could be used.

Do you want to pass your homogenous arrays/sets to functions with rest-parameters or ones with a fixed number of arguments? I'd like more detail on the thing you would be calling.

First of all, I have playing around with the spread operator this morning and most of my use cases works fine in the latest version of typescript [email protected] and on http://www.typescriptlang.org/Playground. So this is not a big deal anymore.

However,
I don't like the idea to make all parameters optional just to be able to use
spread operator BUT I understand the problem; the length of arrays is not known at compile time so
it is impossible to check the parameters at compile time.

However, I think it would be nice if the parameters don't need to be optional and that the compiler
only check that "spread-variable" is of right type eg. number[]

function fun1(x: number, y: number, z: number) {
    return x+y+z;
}

let arr1 = [1, 2, 3];
let arr2 = [1, 2];

fun1(...arr1);
fun1(...arr2); // OK since `arr2` is number[]
fun1(1, 2); // Should cause an error

Is that possible?

The following code works in the current version of typescript, which it didn't when this issue was addressed.

function fun1(...num) {
    return Math.max(...num);
}

function fun2(a1, ...num) {
    return Math.max(...num);
}

function fun3(a1, ...num) {
    return fun1(...num);
}

let arr = [1, 2, 3];

if (Math.random() < .5) {
    arr.push(1);
}

fun1(...arr);
fun2('first param', ...arr);
fun3('first param', ...arr);

Maybe a more realistic example (that also works nowadays):

const doSomeWork = ({title, genre, runtime}) => { console.log(title, genre, runtime); };

function fun1(...num) {
    num.forEach(doSomeWork);
}

const data = [{
    title: 'title',
    genre: ['action', 'drama'],
    runtime: 100
}];

fun1(...data);

:+1: Having the same issue, with the specific use case of the Date constructor:

let dateNumberArray: Array<number> = [2015,11,11];
let myNewDate = new Date(...dateNumberArray);

For us we use spreads to allow users to configure argv requirements in a base app without the need to build their own helpers. For example:

{
  "yargv": [
    [
      "path",
      {
        "demand": true,
        "describe": "Source",
        "default": ".",
        "alias": "p"
      }
    ]
  ]
}
get appArgs() : { yargv: Array<Array<any>>, chdir: Array<boolean | string> } {
  return require(
    "../args.json"
  )
}

argv() : Promise<{}> {
  return new Promise((resolve) => {
    this.appArgs.yargv.forEach(v => yargs.option(...v))
    return resolve(yargs.usage("$0 [args]").help("h").
      alias("h", "help").argv)
  })
}

A real world example using the combineLatest function of https://github.com/ReactiveX/rxjs where I want to preserve the this value when calling projectFn using an arrow function and use rest parameters to propagate the arguments.

getCombination() {
    return this.firstObservable
      .combineLatest(this.secondObservable, (...args) => this.projectFn(...args)));
  }

Currently I have to do

getCombination() {
    return this.firstObservable
      .combineLatest(this.secondObservable, (...args) => this.projectFn.apply(this, args)));
  }

FWIW, another real world example is trying to deep merge an array of objects using merge from lodash:

import { merge } from 'lodash';

...
return merge(...arrayOfObjects);

Here's another real world use case; we're trying to refactor code that looks like this:

return Observable
      .forkJoin(a, b)
      .map(([current, past]) => this.mergeData(current, past));

Into the slightly more elegant:

return Observable
      .forkJoin(a, b)
      .map(data => this.mergeData(...data));

But using the spread operator triggers the signature mismatch error.

Another example

class Parent {
    constructor(a, b, c){

    }
}

class Child extends Parent {
    constructor(d, ...args) {
        super(...args);
    }
}

TS2346: Supplied parameters do not match any signature of call target.

Same issue with overload methods:

listen(port: number, hostname?: string, backlog?: number, listeningListener?: Function): Server;
listen(port: number, hostname?: string, listeningListener?: Function): Server;
listen(port: number, backlog?: number, listeningListener?: Function): Server;
listen(port: number, listeningListener?: Function): Server;
listen(path: string, backlog?: number, listeningListener?: Function): Server;
listen(path: string, listeningListener?: Function): Server;
listen(options: ListenOptions, listeningListener?: Function): Server;
listen(handle: any, backlog?: number, listeningListener?: Function): Server;
listen(handle: any, listeningListener?: Function): Server;

while called as

myListen(...args) {
    listen(...args);
}

then: _[ts] Supplied parameters do not match any signature of call target._

So for now it's impossible to do such thing:

export type DateProp = Date | (string|number)[];

const setDate = (value: DateProp): Date => (
    isDate(value) ? value : new Date(...value)
);

As it is producing error:

Supplied parameters do not match any signature of call target.

Even if value contains correct Date contructor parameters values @mhegazy ? Or I'm not getting something?

Even if value contains correct Date contructor parameters values @mhegazy ? Or I'm not getting something?

That is correct, this is what this issue is tracking.

I feel obliged to ask if I can help / contribute somehow. If now, can you elaborate why exactly it's causing an error in this case? Can it be skipped somehow through config?

I was actually hit with this issue when I renamed a .js file to .ts. Anyhow, one example of "not all valid JS is valid TS".

I'm currently having this issue implementing styled-component's media templates example.

import {css} from 'styled-components';

export const Breakpoints = {
    tablet: 580,
    desktop: 800,
};

export type BreakpointLabels = keyof typeof Breakpoints;

export const media = Object.keys(Breakpoints).reduce((mediaQueries, label: BreakpointLabels) => (
    {
        ...mediaQueries,
        [label]: (...args: any[]) =>
            css`
                @media (max-width: ${Breakpoints[label]}px) {
                    ${css(...args)}
                      ^^^^^^^^^^^^ Supplied parameters do not match any signature of call target.
                }
            `
    }
), {});

My workaround is to use css.call, which at least works with the any[]-typed args:

import {css} from 'styled-components';

export const Breakpoints = {
    tablet: 580,
    desktop: 800,
};

export type BreakpointLabels = keyof typeof Breakpoints;

export const media = Object.keys(Breakpoints).reduce((mediaQueries, label: BreakpointLabels) => (
    {
        ...mediaQueries,
        [label]: (...args: any[]) =>
            css`
                @media (max-width: ${Breakpoints[label]}px) {
                    ${css.call(this, ...args)}
                }
            `
    }
), {});

same issues, my example is basic merging function:

merge(target: T, ...sources: T[])

is there any workaround how to mute this error at least per file?

Actually, I'd like to see reasons why this ES6 possibility is broken by design or any plans when it might be reconsidered. @mhegazy please comment.

We have recently made a change to allow spreading into call expressions if the target is all optional (see https://github.com/Microsoft/TypeScript/pull/15849). The change works by treating a spread output as an infinite set of optional arguments.

With this change, here is the current state in examples:

declare var args: number[];

function foo(x?: number, y?: number, z?: number) { }
foo(...args);     // OK
foo(2, ...args);  // OK

function bar(...args: number[]) { }
bar(...args);     // OK
bar(2, ...args);  // OK

function baz(x: number, y: number, z: number) { }
baz(...args);     // still not allowed

In the last example, the compiler has no way to validate that args satisfy the required baz, since the length of args can not be statically validated.

What is left in this area is to allow tuples with known size to satisfy functions with required arguments of the same length.. but this is not as simple as it may sound. e.g.:

function baz(x: number, y: number, z: number) { }
var tuple: [number, number, number] = [1, 2, 3];
baz(...tuple);     // should be allowed

I have same problem like @devrelm

I'm still having problem with this, what's wrong?

class A {
    constructor(message: string, test?: any) {
        console.log('A constructor called');
    }
}

class B extends A {
    constructor(...spread) {
        super('a', ...spread);
    }
}

It gives an error on typescript playground

@owlcode this is in TS 2.4 but the playground is on TS 2.3 until after 2.4 releases.

This does appear to be fixed in TS 2.4,

export function log(...args: any[]) {
    console.log(...join(args.map(formatDevTools),' '));
}

However, there appears to be a new bug in TS 2.4:

TS2461: Type 'Iterable' is not an array type.

Happens when you try to spread an iterable ([...obj]).

And yet a different bug (I think) in 2.4.1-insiders.20170615 which I haven't been able to figure out yet.

I don't know how to get around this one either. Casting our arrays to any[] won't help.


Nevermind, we can fix the definition of Console,

interface _Console {
    assert(test?: boolean, message?: string, ...optionalParams: any[]): void;
    clear(): void;
    count(countTitle?: string): void;
    debug(...optionalParams: any[]): void;
    dir(value?: any, ...optionalParams: any[]): void;
    dirxml(value: any): void;
    error(...optionalParams: any[]): void;
    exception(message?: string, ...optionalParams: any[]): void;
    group(groupTitle?: string): void;
    groupCollapsed(groupTitle?: string): void;
    groupEnd(): void;
    info(...optionalParams: any[]): void;
    log(...optionalParams: any[]): void;
    msIsIndependentlyComposed(element: Element): boolean;
    profile(reportName?: string): void;
    profileEnd(): void;
    select(element: Element): void;
    table(...data: any[]): void;
    time(timerName?: string): void;
    timeEnd(timerName?: string): void;
    trace(...optionalParams: any[]): void;
    warn(...optionalParams: any[]): void;
}

And then cast the console object:

export function log(...args: any[]) {
    (console as _Console).log(...join(args.map(formatDevTools),' '));
}

That'll have to do for now.

I opened a PR for this at #18004.

For now, I'll have to enable "noStrictGenericChecks" in my tsconfig.

For now, I'll have to enable "noStrictGenericChecks" in my tsconfig.

Not sure I see how this is related to this bug...

Not sure I see how this is related to this bug.

@mhegazy This setting disables all function signatures, so I can pass arguments using the spread operator without matching the amount of arguments (at least not statically)

that is not what --noStrictGenericChecks do. Please see https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#stricter-checking-for-generic-functions

I just ran across this.

While I can understand that it is difficult, at least "excess" arguments should be able to identify.

@sandersn from a real world perspective we were refactoring an API to either accept a single argument or an array, instead of using a rest argument. So situations where we were passing something like foo(value, ...otherValues) were not being identified as errors. Specifically this seems really dangerous:

declare const foo: (value: string) => string;

foo('bar', 'qat'); // Expected 1 arguments, but got 2.

foo('bar', ...['qat']); // No error

@kitsonk can you open a separate bug for that? It would be easier to consider in isolation, and is probably a small tweak to the existing rules, rather than a complex change.

Even if I check the length, I still get this error:

function myFunc(...args: any[]) {
  if(args.length > 0) {
    otherFunc(...args)
  }
}

Edit:

To clear up confusion, I was redirected from here, which states:

Expected 2 arguments, but got a minimum of 0

That issue was considered a duplicate of this issue.

In my case, I was able to fix my error (more like a hack than a fix) by allowing for nothing to be passed to the method by adding function otherFunc() as one of the initializers like so:

function otherFunc()
function otherFunc(arg1: string)
function otherFunc(arg1: string, arg2: number)
function otherFunc(...args: any[]) {
  // Do stuff
}

This workaround becomes broken on 2.7

How is this still an issue? Spread operators are a perfectly compliant way to work in JS, so TS should account for it properly.

@jpike88 I know you mean well, but you might not realize statements like these are counter productive. The issue is on their TODO for milestones 2.7 or 2.8 it looks like. It might seem like an obvious thing to fix, but when you have thousands of obvious things to fix you can't possibly fix them all at the same time, and continue to improve other things that are expected of them. The best way to voice your vote is to give the original post at the top a 👍 reaction. This is free software after all and they don't owe us anything. That said, that's just my take, another user
of TS like you 🕺

Another nice testcase (actually a regression of TS 2.7) is this:

class NonEmptyArray<T> extends Array<T> {
        0: T;
}

function c(firstArg: string, ... plainValues: string[]): string;
function c(): undefined;
function c(...values: string[]): string | undefined {
        if (!values.length) {
                return undefined;
        }
        return "";
}


function d(): NonEmptyArray<string> {
        return [""];
}

function y(): string | undefined {
        return c(...d());
}

It produces

test.ts(20,9): error TS2557: Expected at least 0 arguments, but got 0 or more.

Another issue I came across regarding spread operator:

const get = (id?: string = '1231254', bar?: Bar, baz?: Baz) => {...}
const foo: [undefined, Bar, Baz] = [undefined, bar, baz]

get(...foo) // [ts] Argument of type 'Bar | Baz' is not assignable to parameter  
of type 'string'.
Type 'Baz' is not assignable to type 'string'

Of course, in ES6 I have no problem to spread array with undefined included as arguments and pass properly the undefined but it seems like TS just ignore undefined values when he spread them.

Another real world example, in which correct handling of spread operator in function call will help a lot:
I have functions generated by NSwagStudio - API generator for C# WebApi, which have the same parameters (GET parameters are defined in structure)

The generated function looks like following:

export interface ITableApiClient {
    getTableInfo(objnam: string | null, objsch: string | null | undefined, dbnam: string | null, conid: number | undefined): Promise<FileResponse | null>;
    otherFunction(objnam: string | null, objsch: string | null | undefined, dbnam: string | null, conid: number | undefined): Promise<string | null>;
}

I want to call it with syntax

   get tableIdentification() {
       return [this.name, this.schema, this.databaseName, this.serverConnectionId];
   }
...
   return apiClient.getTableInfo(...this.tableIdentification);

   // this doesn't help, still compile-time error
   // return apiClient.getTableInfo(...(this.tableIdentification as any));

because there are more functions with exactly the same parameters (because they are generated from the same parameters class defined in C# backend). Now I have to copy body of property tableIdentification n-times into each usage

@smashdevcode For me solution was to add @ts-ignore

function foo(x: number, y: number, z: number) { }
var args = [0, 1, 2];
// @ts-ignore
foo(...args);

example here: https://stackblitz.com/edit/typescript-ecymei?embed=1&file=index.ts

@darekf77, or if you say that args is a tuple:

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

ts-playground

@darekf77, or if you say that args is a tuple:

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

This works for me when I compile typescript in the command line, but not when Visual Studio Code validates the typescript while I'm typing. I'm not sure what the disconnect is here.

I see it's been over a year since jayphelps mentioned that this would be fixed in an upcoming release, but this issue still occurs. Is there any update on this? I know I could use @ts-ignore however this removes ALL typescript messages for the function so isn't really an appropriate solution.

@TidyIQ as mentioned by tjoskar, typecast your spread array as a tuple.

The array isn't fixed-length so that won't work.

A typecast will work as far as the compiler is concerned, even if it is innacurate. I agree it's not the best solution.

(This question should probably be moved to stack overflow (or similar))

@TidyIQ, if the array doesn't have a fix length, the compiler can not tell if the array is suitable or not.

function foo(x: number, y: number, z: number) { }
const args = arrayOfSomeLength;
foo(...args); // Error: The compiler do not know if there is 3 or 2 or 1 or 50 elements in the array. 

So if the array is of dynamic length the arguments should be that as well:

function foo(...args: number[]) { }
const args = arrayOfSomeLength;
foo(...args);

Playground

Unfortunately that won't work either as some of my args are fixed length.

Perhaps if I just post the code you might see the issue.

interface IF_Object {
  [key: string]: object;
}
//  VALUE SHOULD BE REQUIRED BUT MADE OPTIONAL TO BYPASS TYPESCRIPT ERROR
interface IF_SetKV {
  (key: string, value?: any): Function;
}
//  VALUE SHOULD BE REQUIRED BUT MADE OPTIONAL TO BYPASS TYPESCRIPT ERROR
interface IF_CreateState {
  (key: string, value?: any, ...more: string[]): Function;
}

const setKV: IF_SetKV = (key, value) => (object: IF_Object = {}) => ({
  ...object,
  [key]: value
});

const createState: IF_CreateState = (key, value, ...more) => (
  object: IF_Object = {}
) =>
  more.length === 0
    ? setKV(key, value)(object)
    : setKV(key, createState(value, ...more)(object[key]))(object);

// TYPESCRIPT ERROR OCCURS HERE. CAN ONLY BE REMOVED WITH TS@IGNORE
const newState = createState(...stateList, action.payload.value)(reduced);

@TidyIQ, I'm not sure I follow. What happens if stateList is an empty array? In that case will action.payload.value be passed as the key?

Should not this work:

const createState = (...args: string[]) => (object: IF_Object = {}) => {
  const [key, value, ...rest] = args;
  if (rest.length === 0) {
    setKV(key, value)(object)
  } else {
    setKV(key, createState(value, ...rest)(object[key]))(object);
  }
}

Cast to spread value as the ParameterType of the function you are passing arguments to.

const add = (a: number, b: number) => a + b

const values = {
  a: 1,
  b: 2,
}

type AddParams = Parameters<typeof add>

add(...(values) as AddParams)

@mateja176 This also seems to work and might better suit some use cases.

add(...(values as [number,number]))

or

add(...(values as [any,any]))

I use a helper type for this to handle arbitrary arrays without having to type them explicitly:

type NonEmptyArray<T extends any[]> = T extends (infer U)[]
  ? [U, ...U[]]
  : never

It can be used like this:

add(...values as NonEmptyArray<typeof values>)


Detailed explanation for beginners

The following dissection of the NonEmptyArray type explains how it works in detail:

# | Part | Explanation
-|-|-
(1) | type NonEmptyArray | The name of the helper type
(2) | <T | The helper type takes a generic type parameter T.
(3) | extends any[]> | To be accepted by the type checker, that type parameter T must be some array type.
(4) | T extends (infer U)[] ? | Our helper type is a conditional type which checks whether T actually is an array type.
We have declared T as an array type in (3), so this condition always passes, but using the conditional allows us to let TypeScript infer the type that the T array is made of, and we call that type U.
(5) | [U, ...U[]] | Now we can assemble the resulting type: an array where the first entry is of type U and the remaining (0 or more) entries are also of type U.
Because of this specific tuple notation, TypeScript is aware that there is at least one item.
(6) | : never | This is just needed to syntactically complete the conditional. Remember: the conditional is just a trick to extract the U type, and it always passes. Therefore, we can safely ignore the "else" branch by yielding never.


Now if we do this...

ts const values = [1,2,3] add(...values as NonEmptyArray<typeof values>)

...the following will happen:

  • typeof values is the type which TypeScript inferred for the values array, which is an array of numbers: number[].
  • That means, we pass number[] as T. (2/3)
  • T is indeed an array type, so, we can infer U from it, which is number. (4)
  • Now that we know U is a number, we yield the type [number, ...number[]]. (5)

@tjoskar change your code to

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

(playground)
And the error's back.

Surprisingly, if you were to change the last line to foo(3, ...args); - there will be no error.

I feel like this still isn't working. Here is my example

onSetValue={(...args: Parameters<typeof props.onSetValue>) => {
    setLanguage(null);
    props.onSetValue(...args); // Expected 2 arguments, but got 0 or more.
  }}

It shouldn't really matter what is the type of props.onSetValue, because I just take the parameters type and pass it to the function which I got the the type from and it still gives the Expected 2 arguments, but got 0 or more. error.

Playground link

Here is a reduced form of @Haaxor1689's example:
Playground link

I still can't get it working

this is my temporary workaround

class Board {
  private events: Events

  public on(...args: Parameters<this['events']['on']>) {
    this.events.on.call(this.events, ...args)
  }
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

fwanicka picture fwanicka  ·  3Comments

wmaurer picture wmaurer  ·  3Comments

Roam-Cooper picture Roam-Cooper  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

manekinekko picture manekinekko  ·  3Comments