selimb
selimb

Reputation: 370

Return subset of union depending on parameter in generic function

Given the classic discriminated union:

interface Square {
    kind: 'square';
    width: number;
}

interface Circle {
    kind: 'circle';
    radius: number;
}

interface Center {
    kind: 'center';
}


type Shape = Square | Circle | Center;

How can I type the following filter so that the return type is narrowed down to all Shape that have a kind in kinds.

function filterShape(kinds: Array<Shape["type"]>, shapes: Array<Shape>): Array<?> {
  return shapes.filter((shape) => kinds.includes(shape.kind))
}

I tried this, but the return type (unsurprisingly) always ends up being Array<Shape>.

function filterShape<S extends Shape>(kinds: Array<S["kind"]>, shapes: Array<Shape>): Array<S> {

for instance the type of s in the call below is Array<Shape>. I would like Array<Center | Circle>.

const s = filterShape(["center", "circle"], shapes)

You'll find a TS playground with the above code at https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMoEcCucooN4BQyxyA1qACYBcyA5AM5Y4S0DcBAvgQaJLIigDCwKAgA2+IiXIhqdBCPEt2XHuGjwkyQRHVRkhEmUo1aSPW07cCYAJ4AHFKgAWcR8gC8aJrmQAfbUUJf21dPnYCGEwQBDBgAHsQZBhgMT4XNwgAHlRkCAAPSFl6NFdHAD4AChkKehoAQSgoOFscgG0AIhqOgF1ygBpkejKIOuRG5taMioBKBqaWnPKDKVwwTCgk4cz6ADoUtOhKyu3HGc9lmr3QcUwKUZOR3ZqZ87gSicXUcpVuBET6GAhiMxp8piNll42ngajQOoxsLgOhxBjCTMgOgpRBJkajYRjzHxcQZ8ZiwtBkT0Iv8QIChp5kql0iNKp1CRTBpighBeoNTqMZkA

Upvotes: 2

Views: 227

Answers (1)

tenshi
tenshi

Reputation: 26344

This is possible using Extract:

function filterShape<Kinds extends Shape["kind"][]>(kinds: Kinds, shapes: Array<Shape>): Array<Extract<Shape, { kind: Kinds[number] }>> {

Extract all members of the union Shape that are assignable to type { kind: Kinds[number] }.

Kinds is an array of kinds of shapes we want. So Kinds[number] gives us a union of the kinds we want.

const s = filterShape(["center", "circle"], shapes);
//    ^−−−−−−−−− type is `(Circle | Center)[]`

Playground


Note that this only works when kinds is a compile-time constant value, as it is in your question. This would not work:

const kinds: Shape["kind"][] = ["center", "circle"];
const s2 = filterShape(kinds, shapes);
//    ^^−−−−−−−− type is `(Square | Circle | Center)[]` (basically, `Shape[]`)

But with compile-time constant values, this is very handy.

Playground

Upvotes: 4

Related Questions