Typescript: Unable to reconstruct function type from Parameters and ReturnType

Created on 4 Feb 2019  路  2Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.4.0-dev.20190202


Search Terms:

  • 2322
  • Parameters ReturnType

Code

function wrap<Fn extends (...args: any) => any>(fn: Fn): Fn {
  type P = Parameters<Fn>;
  type R = ReturnType<Fn>;
  return function(...args: P): R {
    return fn(...args);
  };
}

Expected behavior:

I would expect this to pass the type checker. I'm returning a function with the same parameter and return type as Fn, so I'd expect it to be assignable to Fn.

Actual behavior:

I get this error:

[ts] Type '(...args: Parameters<Fn>) => ReturnType<Fn>' is not assignable to type 'Fn'. [2322]
utils.ts(369, 10): Did you mean to call this expression?

Playground Link:

playground link

Related Issues:

The full context is that I want to write a wrapper which memoizes just the last invocation of a function. I wound up with:

export function memoizeOne<Fn extends (...args: any) => any>(fn: Fn): Fn {
  type P = Parameters<Fn>;
  type R = ReturnType<Fn>;
  let lastArgs: P = null;
  let lastResult: R;

  return function(...args: P) {
    if (shallowEqual(args, lastArgs)) {
      return lastResult;
    }

    lastArgs = args;
    lastResult = fn(...args);
    return lastResult;
  } as any;  // XXX
}

but I don't think that as any should be needed.

Working as Intended

Most helpful comment

The contract associated with a function of type <Fn extends (...args: any) => any>(fn: Fn): Fn is _very_ strong; the only real way to return a value of type Fn is to return the value you were given.

If a caller were to pass a function with properties then the type suggests they would get back a value with the same properties: the wrap function does not do this.

interface A {
  prop: number;
  (x: number): number;
}
declare const a: A;
const p: number = wrap(a).prop;

I think the best bet is to be directly parametric in the input and output types.

function wrap<P extends any[], R>(fn: (...args: P) => R): (...args: P) => R {
  return function(...args: P): R {
    return fn(...args);
  };
}

All 2 comments

The contract associated with a function of type <Fn extends (...args: any) => any>(fn: Fn): Fn is _very_ strong; the only real way to return a value of type Fn is to return the value you were given.

If a caller were to pass a function with properties then the type suggests they would get back a value with the same properties: the wrap function does not do this.

interface A {
  prop: number;
  (x: number): number;
}
declare const a: A;
const p: number = wrap(a).prop;

I think the best bet is to be directly parametric in the input and output types.

function wrap<P extends any[], R>(fn: (...args: P) => R): (...args: P) => R {
  return function(...args: P): R {
    return fn(...args);
  };
}

Makes perfect sense, thanks for the explanation @jack-williams!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

weswigham picture weswigham  路  3Comments

jbondc picture jbondc  路  3Comments

siddjain picture siddjain  路  3Comments