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

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.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 176
  • Comments: 57 (12 by maintainers)

Commits related to this issue

Most upvoted comments

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

@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 🕺

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

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)}
                }
            `
    }
), {});

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

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)

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.

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.

@ahejlsberg this issue either persists or there is a regression in the latest TS version. The comment above (https://github.com/microsoft/TypeScript/issues/4130#issuecomment-691115905) demonstrates it perfectly.

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

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

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

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

or

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

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

I feel like this is still a problem (just encountered this). Is there another open issue for this, or would it be relevant to re-open this one?

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

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

@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

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

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

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

@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

I opened a PR for this at #18004.

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<any>’ 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.

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