Typescript: Function argument types only inferred for mapped function with the maximum number of arguments

Created on 25 Jul 2017  路  4Comments  路  Source: microsoft/TypeScript



TypeScript Version: 2.4.1

Code

interface MyEvents {
    'zero'  : () => void;
    'one'   : (arg1: string) => void;
    'two'   : (arg1: string, arg2: string) => void;
    'three' : (arg1: string, arg2: string, arg3: string) => void;
    'four'  : (arg1: string, arg2: string, arg3: string, arg4: string) => void;
}

interface MyEmitter<T> {
    on<K extends keyof T>(event: K, fn: T[K]): this;
    once<K extends keyof T>(event: K, fn: T[K]): this;
}

declare const myEmitter: MyEmitter<MyEvents>;

myEmitter.on('zero'  , () => {});
myEmitter.on('one'   , (arg1) => {});
myEmitter.on('two'   , (arg1, arg2) => {});
myEmitter.on('three' , (arg1, arg2, arg3) => {});
myEmitter.on('four'  , (arg1, arg2, arg3, arg4) => {});

Expected behavior:
Types are inferred for all event callback arguments.

Actual behavior:
Types are inferred only for the callback arguments of the event 'four'. If 'four' is removed from MyEvents, then only the callback arguments for 'three' are inferred. If 'three' is removed, then only the arguments for 'two' are inferred. etc.

The order of properties on MyEvents has no effect, and if there are multiple events with the maximum number of arguments they are all inferred correctly.

EDIT: I just noticed adding another function with the same number of arguments, but different types also prevents arguments from being inferred, so this is probably an issue with merging the type signatures. Interestingly IntelliSense (both in VSCode, and the online playground) can still infer the proper types.

Needs Investigation

Most helpful comment

This works now

All 4 comments

This does seem buggy to me. What's a minimal example of it?

interface Functions {
  's': (s: string) => void;
  'ns': (n: number, s: string) => void;
}
declare function getFunction<K extends keyof Functions>(key: K, func: Functions[K]): void;
getFunction('ns', (x, y) => { x.random; y.random }); // good errors, x is number, y is string
getFunction('s', (x) => { x.random }); // bad, x has any type?!
getFunction('s', (x: number) => { }); // good error, string is not assignable to number
getFunction('s', (x: string) => { }); // workaround, no error
getFunction('s', (x => {}) as Functions['s']); // workaround, no error

It's strange to me that the compiler does infer the right function type but does not infer the parameter types for that function. Obviously you can work around it by specifying the type on the parameter(s), but it seems eminently inferable to me. Is this a bug or a weird limitation?

This is still an issue in version 3.2.0-dev.20180927

Repro:

interface FnMap {
  a: (x: string) => any;
  b: (x: number) => any;
}

declare function on<K extends keyof FnMap>(key: K, fn: FnMap[K]): void;

on('a', x => console.log(x));  // Bad: x is not inferred
on('b', x => console.log(x));  // Bad: x is not inferred

declare function handle_it(x: string): void;

on('a', handle_it);            // Good
on('b', handle_it);            // Good - Error

It looks like the actual type mapping is fine, since using the explicitly typed function produces the expected results, so the problem is with the type inference of the arguments of the anonymous functions.

@yseymour Nice! Here's a slight variation on your example that I think provides some additional insight:

interface FnMap {
  a: (x: string) => any;
  b: (x: number, y: number) => any;
}

declare function on<K extends keyof FnMap>(key: K, fn: FnMap[K]): void;

on('a', x => console.log(x));          // Bad: x is not inferred
on('b', (x, y) => console.log(x, y));  // Good: x and y are inferred

I think the core of the problem might be that the callback provided for a (with implicitly typed x) is also valid for b. On the other hand, the callback provided for b expects more arguments than the expected callback for a, so it's only valid for b and inference works properly.

This works now

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Zlatkovsky picture Zlatkovsky  路  3Comments

blendsdk picture blendsdk  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

siddjain picture siddjain  路  3Comments

manekinekko picture manekinekko  路  3Comments