Home > Software design >  Typescript: infer generic types in generic type constraint
Typescript: infer generic types in generic type constraint

Time:11-23

I have a generic interface that takes a type argument that must extend another generic type. For example :

export interface IPoint<TX, TY>
{
    x: TX;
    y: TY;
}
export interface ISeries<TPoint extends IPoint>
{
    points: Array<TPoint>;
}

Here, I must specify TX and TY for IPoint.

My question is : is there a way to infer those types automatically, for example like this ?

export interface ISeries<TPoint extends IPoint<infer TX, infer TY>>
{
    points: Array<TPoint>;
}

The only way to make this work I've found for now is to add TX and TY as type parameters for ISeries, but it's quite impractical because then I have to specify the three types every time I use the interface.

I could also use IPoint<any, any>, but then I lose the information about the real types of x and y.

EDIT : To add some clarification about what I want to achieve, let's consider the following example :

export interface ISeries<TPoint extends IPoint<infer TX, infer TY>>
{
    points: Array<TPoint>;
    transformYValues?: (yValue: TY) => number;
}

Here I would need TY to strongly type transformYValues.

Thanks for your help

EDIT 2 : Found a solution (thanks captain-yossarianfromUkraine).

export interface ISeries<TPoint extends IPoint<any, any>>
{
    points: Array<TPoint>;
    transformYValues?: (yValue: TPoint['y']) => number;
}

The key here is TPoint['y'], I wasn't aware of this neat feature called Indexed Access Types (see https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html).

In summary => use any typing in the generic type constraints, then use indexed access types to strongly type methods/properties inside the interface.

CodePudding user response:

Consider this example:

export interface Point<X, Y> {
  x: X;
  y: Y;
}

type Values = string | number

export interface Series<Points extends Point<Values, Values>[]> {
  points: Points;
  transformYValues: (yValue: Points[number]['y']) => number;
}

const inference = <
  X extends Values,
  Y extends Values,
  Points extends Point<X, Y>[]
>(data: Series<[...Points]>) => void 0


inference({
  points: [{ x: 1, y: 2 }, { x: 1, y: 9 }],
  transformYValues: (arg) => {
    arg // 2 | 9
    return 42
  }
})

PLayground

I added Values type as a constraint for allowed x and y data types.

Also, I have added a function inference because type inference works with generic conditional types and function arguments.

To infer literal type x:1 y:2 I have used variadic tuple types, see this [...Points]

  • Related