[SOLVED] Typescript – Failed to infer argument type

Issue

I’m trying to implement clamp for multiple number-ish types simultaneously like so:

import BigNumber from 'bignumber.js'

export const clamp = <T extends number | BigNumber>(min: typeof n, n: T, max: typeof n): T => {
  if (isNumber(n)) {
    return Math.max(min, Math.min(n, max)) as T
  }

  if (isBigNumber(n)) {
    return BigNumber.max(min, BigNumber.min(n, max)) as T
  }
}

const isNumber = (n: number | BigNumber): n is number => {
  return typeof n === 'number'
}

const isBigNumber = (n: number | BigNumber): n is BigNumber => {
  return n instanceof BigNumber
}

But code failes to compile with the following error:

TypeScript error in clamp.ts(5,21):
Argument of type 'number | BigNumber' is not assignable to parameter of type 'number'.
  Type 'BigNumber' is not assignable to type 'number'.  TS2345

    3 | export const clamp = <T extends number | BigNumber>(min: typeof n, n: T, max: typeof n): T => {
    4 |   if (isNumber(n)) {
  > 5 |     return Math.max(min, Math.min(n, max)) as T
      |                     ^
    6 |   }
    7 | 
    8 |   if (isBigNumber(n)) {

Shouldn’t types of min and max be inferred as number on line 5? If not, how can one assure Typescript of correctness of types?

Solution

Unfortunately this is one of the cases where Typescript’s type system comes short.
There is a proposal to allow intersection type guards, this would enable us to have a type guard such as:

const isNumber = (n: number | BigNumber, min: typeof n, max: typeof n): n is number & min is number & max is number => {
    return typeof n === 'number';
}

Until such proposal is implemented, you’ll have to explicitly type-check both min and max variables:

import BigNumber from 'bignumber.js'

export const clamp = <T extends number | BigNumber>(min: T, n: T, max: T): number | BigNumber => {
    if (isNumber(n) && isNumber(min) && isNumber(max)) {
        return Math.max(min, Math.min(n, max));
    }

    if (isBigNumber(n) && isBigNumber(min) && isBigNumber(max)) {
        return BigNumber.min(min, BigNumber.max(n, max));
    }

    throw new TypeError("Every parameter of this function must be either of type number or an instance of BigNumber");
}

const isNumber = (n: number | BigNumber): n is number => {
    return typeof n === 'number'
}

const isBigNumber = (n: number | BigNumber): n is BigNumber => {
    return n instanceof BigNumber
}

or cast both min and max:

import BigNumber from 'bignumber.js'

export const clamp = <T extends number | BigNumber>(min: T, n: T, max: T): number | BigNumber => {
    if (isNumber(n)) {
        return Math.max(min as number, Math.min(n, max as number));
    }

    return BigNumber.min(min as BigNumber, BigNumber.max(n, max as BigNumber));
}

const isNumber = (n: number | BigNumber): n is number => {
    return typeof n === 'number'
}

Answered By – Joao Pedro Braz

Answer Checked By – Mary Flores (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *