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)