1

What I want to do is pass an array to a function and receive the values of this array as a tuple in another function.

myFunction(
    (a, b) => null,
    {
        params: [true, 1]
    }
);

In the example above, I want typings: a: boolean and b: number. Instead I receive a: boolean | number and b: boolean | number.

Please keep in mind that I want the length of params to be variable. Also [1, false, 'string', {}] should be possible.

As I have read this has something to do with mapped tuples? But I really don't get it.

My current implementation looks like this:

function myFunction<P extends any[]>(
    fn: (...params: P) => null, 
    options: { params: P }) {
    ...
}

--

What actually works is this:

myFunction(
    (a, b) => null,
    {
        params: [true, 1] as [boolean, number]
    }
);

But I really don't want to type-cast all the time to make it work :/

misantronic
  • 1,132
  • 2
  • 10
  • 23

3 Answers3

2

Update:

There is actually an easier way. Normally narrowing of an array to a tuple type is only possible by the caller of a function with as const (see original answer). We can move this responsibility to the generic type parameter P of function myFunction by including a tuple type as constituent of an uniontype, so the constraint now becomes P extends (ReadonlyArray<any> | readonly [any]).

function myFunction<P extends (ReadonlyArray<any> | readonly [any])>(
  fn: (...params: P) => null,
  options: { params: P }
) {
  fn(...options.params)
  // ...
}

myFunction((a, b) => null, {
  params: [true, 1]
});

// params: [boolean, number]
// fn: (a: boolean, b: number) => null

Playground

Credit goes to an answer by jcalz involving some "sorcery code" I picked up here. Also have a look at the relevant feature suggestion.

Original answer:

Although I don't quite understand your use case, what about this?

// fn: (params_0: true, params_1: 1, params_2: Date) => null
myFunction((a, b) => null, {
  params: ([true, 1, new Date()]) as const
}); 

function myFunction<P extends readonly any[]>(
  fn: (...params: P) => null,
  options: { params: P }
) { fn(...options.params); }

You still use a const assertion, but don't have to manually write all tuple item types. I assume, you just pass on your params directly to the callback, as there would be no way to manipulate P inside myFunction body in a way, where you rely on the tuple item types, as P is only constrained to extend any[].

In general initialized arrays are widened to Array type by the compiler if you don't narrow them at initialization time with as const.

const options = {
  params: [3,4,"a"] // params: (string | number)[];
}

const optionsConst = {
  params: [3,4,"a"] as const // params: readonly [3, 4, "a"];
}

Playground

Community
  • 1
  • 1
ford04
  • 66,267
  • 20
  • 199
  • 171
0

Your declaration code is correct.

This is the right way to invoke your function:

myFunction<[number, boolean]>(
  (a, b) => null, 
  {params: [1, true]}
);
david_p
  • 5,722
  • 1
  • 32
  • 26
0

try this:

function myFunction<A, B, P extends [A, B]>(
  fn: (...params: P) => null,
  options: { params: P }) {
     ...
}
D Pro
  • 1,756
  • 1
  • 8
  • 15