Thibault Walterspieler
Thibault Walterspieler

Reputation: 2402

Conditional interface on parameter type

I'm trying to make a typescript function with a conditional return type. My function need to accept 2 types : BotLotteryPrize[] and Lot[] and return BotLotteryPrize or Lot accordingly.

Interfaces

BotLotteryPrize:

export type BotLotteryPrize = {
  id: string
  videoUrl: string | null
  probabilityToWin: number
  ...
}

Lot:

export type Lot = {
  id: string
  probabilityToWin: number
  credit: number | null
  ...
}

So I tried to write that :

type BotLotteryPrizeOrLot<T extends (BotLotteryPrize | Lot)[]> =
  T extends BotLotteryPrize[] ? BotLotteryPrize : Lot;
function raffleDrawWithDistribution<T extends Array<BotLotteryPrize | Lot>>(
  items: T,
): BotLotteryPrizeOrLot<T> {
  const randomInt = this.generateRandomInt();

  const sumOfProbabilities = items.reduce(
    (acc, currentItem) => acc + currentItem.probabilityToWin,
    0,
  );

  if (sumOfProbabilities !== 1) {
    items.forEach((item) => {
      item.probabilityToWin = item.probabilityToWin / sumOfProbabilities;
    });
  }

  const probabilities = items.reduce(
    (acc, currentLot, idx) => {
      const nextProbability = acc[idx] + currentLot.probabilityToWin;
      return [...acc, nextProbability];
    },
    [0],
  );

  let wonItem: Lot | BotLotteryPrize = null;

  items.forEach((item, idx) => {
    if (randomInt > probabilities[idx] && randomInt <= probabilities[idx + 1]) {
      wonItem = item;
    }
  });

  return wonItem;
}

Problematic

At the return of my function I get a ts error :

Type 'BotLotteryPrize | Lot' is not assignable to type 'BotLotteryPrizeOrLot<T>'.
  Type 'BotLotteryPrize' is not assignable to type 'BotLotteryPrizeOrLot<T>

Upvotes: 0

Views: 32

Answers (1)

Alex Wayne
Alex Wayne

Reputation: 187034

First of all this type:

type BotLotteryPrizeOrLot<T extends (BotLotteryPrize | Lot)[]> =
  T extends BotLotteryPrize[] ? BotLotteryPrize : Lot;

Is kind of silly. It basically simplifies to:

type BotLotteryPrizeOrLot<T extends (BotLotteryPrize | Lot)[]> = T[number]

And conditional types are notoriously difficult to assign to so this is a big improvement. It's also so simple you don't need it at all.


I say, change your function to this:

function raffleDrawWithDistribution<T extends BotLotteryPrize | Lot>(
  items: T[],
): T {
  //...
}

And then just let items infer the type for you. Calling items.find will return a T | undefined so you don't have to mess with it.

const wonItem = items.find((item, idx) => {
  return randomInt > probabilities[idx] && randomInt <= probabilities[idx + 1]
})

if (!wonItem) throw new Error("Failed to win an item!")

return wonItem;

See Playground

Upvotes: 2

Related Questions