Ivan
Ivan

Reputation: 1284

How do I map return types into a tuple?

I have an array of instances where execute returns a type, here are for example 2 classe whose instances are in the array:

class ClassBase<TReturn>
{
   execute (): TReturn
}

class ReturnsString extends ClassBase<string>
{
   execute () : string
}

class ReturnsNumber extends ClassBase<number>
{
   execute () : number
}

const items:[ReturnsString,ReturnsNumber] = [new ReturnsString(), new ReturnsNumber()];

inside a method i will call execute in a loop for each item in tuple.

How to declare a method that

Upvotes: 2

Views: 517

Answers (3)

justTryIt
justTryIt

Reputation: 67

I recently implemented a similar types:

type TransformTuple<Tuple extends (() => any)[], Returns extends any[] = []> =
  Tuple extends [() => any, ...infer More] ?
    More extends (() => any)[] ?
      TransformTuple<More, [...Returns, ReturnType<Tuple[0]>]>
      : never
    : Returns

type Test = TransformTuple<[() => 1, () => "2"]>
/*
type Test = [1, "2"]
*/

//NOTE Only tuples are supported

//If use normal array like this:
type Test2 = TransformTuple<Array<() => "never">>
/*
//You only get an empty array
type Test2 = []
*/

Upvotes: 1

Pedro Mutter
Pedro Mutter

Reputation: 1224

nice question! It made me think a lot and use many advanced TS typing features, but I guess that this makes what you are looking for:

abstract class ClassBase<TReturn = any>
{
   abstract execute (): TReturn
}

type FlattenIfArray<T> = T extends (infer R)[] ? R : T
type ExtendsOf<T> = T extends ClassBase<infer R> ? R : T
type ExecuteArrayReturn<T> = ExtendsOf<FlattenIfArray<T>>

function example<T extends ClassBase<ExecuteArrayReturn<T>>[]>(items: T): ExecuteArrayReturn<T> {
    return items.map(item => item.execute()) as any
}

class ReturnsString extends ClassBase<string>
{
   execute () : string {return 'a'}
}

class ReturnsNumber extends ClassBase<number>
{
   execute () : number {return 1}
}

const items:[ReturnsString,ReturnsNumber] = [new ReturnsString(), new ReturnsNumber()];

const result = example(items);

I needed to create some types to extract the execute function return value from instances of the ClassBase heirs. FlattenIfArray gets the type of the array items, ExtendsOf get the type of the generic type that has been set to T and ExecuteArrayReturn join both of them.

When you implement your real function, don't forget about the as any on the function return. Otherwise, TS will think that you want to return an array of the provided items types union, for example: (string | number)[].

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249556

You can use mapped types to map over a tuple and extract the return type for each tuple item:

type ReturnsOfClassBase<T extends Record<number, ClassBase<any>>> = {
  -readonly [P in keyof T] : T[P] extends ClassBase<infer R> ? R: never
}
function getReturns<T extends readonly ClassBase<any>[]>(p: T): ReturnsOfClassBase<T> {
  const result = []
  for(let r of p) {
    result.push(r.execute());
  }

  return result as any
}

let r = getReturns(items)

Playground Link

Upvotes: 3

Related Questions