0

I don't know how to get type of parameters of functions which are declared in interface. I need to have proper type checking of them.. Probably I need to use: Parameters class from TypeScript version: 3.3: https://github.com/Microsoft/TypeScript/blob/v3.3.1/lib/lib.es5.d.ts#L1471-L1474 but I don't know how to use it.

interface MyFunctions {
    FIRST_FUNCTION: () => void;
    SECOND_FUNCTION: (string, number) => void;
}

class Params<Functions> {
    private functions: Map<keyof Functions, Set<Functions[keyof Functions]>> = new Map();

    // some other functions...

    public boom<K extends keyof Functions>(func: K, ...args: ???? /* here I don't know how to define type*/ ) {
        this.functions.get(func)(args);
    }
}
Horyzont
  • 59
  • 1
  • 4
  • Could you please edit this question to be a [mcve]? The interface `MyFunctions` is [not defined correctly](https://github.com/Microsoft/TypeScript/wiki/FAQ#why-dont-i-get-type-checking-for-number--string-or-t--t). The implementation of `boom()` seems to be trying to call a `Set` as if it were a function, and is passing it an array `args` instead of what I assume is meant to be a spread. None of these issues have much to do with your question, so it would be nice if you'd clean them up. – jcalz Mar 22 '19 at 17:24

1 Answers1

1

Here's how I'd solve your issue, noting that I had to clean up a lot of stuff and make guesses about what you're doing:

interface MyFunctions {
  FIRST_FUNCTION: () => void;
  SECOND_FUNCTION: (x: string, y: number) => void; // FIXED
}

// constrain Functions to a type holding only function properties
class Params<Functions extends Record<keyof Functions, (...args: any[]) => any>> {

  private functions: Map<keyof Functions, Set<Functions[keyof Functions]>> = new Map();

  // use Parameters as requested
  public boom<K extends keyof Functions>(func: K, ...args: Parameters<Functions[K]>) {
    // assert that it returns a set of the right kind of function
    const funcSet = (this.functions.get(func) || new Set()) as Set<Functions[K]>;

    // okay, and remember to use spread
    funcSet.forEach(f => f(...args));
  }
}

new Params<{a: string}>(); // error, string is not a function

new Params<MyFunctions>().boom("FIRST_FUNCTION"); // okay
new Params<MyFunctions>().boom("SECOND_FUNCTION", "a", 1); // okay

The parts pertaining to your question:

  • I constrained the generic Functions type to Record<keyof Functions, (...args: any[]) => any> so that the compiler knows that all properties of Functions must be functions. This will prevent you from calling new Params<{a: string}>().

  • I have typed the args rest parameter as Parameters<Functions[K]>, where Functions[K] looks up the property of Functions with the key K. Since, due to the generic constraint, the compiler knows that Functions[K] must be a function type, it is happy to allow you to pass it to Parameters<> and return a tuple of the parameters.

I re-wrote the implementation of boom() to make more sense to me. I needed to do a type assertion to convince the compiler that what comes out of this.functions.get(func) is actually a set of Functions[typeof func] as opposed to a set of the wider Functions[keyof Functions]. And I called each function element of the acquired Set, using spread syntax on the arguments. If these assumptions are wrong, hopefully they'll still steer you in a useful direction.

Hope that helps; good luck!

jcalz
  • 264,269
  • 27
  • 359
  • 360