better-typescript-lib icon indicating copy to clipboard operation
better-typescript-lib copied to clipboard

Type guard on `Number.isInteger` is too strong

Open ehoogeveen-medweb opened this issue 3 years ago • 0 comments
trafficstars

I was reading the discussion at https://github.com/microsoft/TypeScript/issues/15048 and noticed that the definition of Number.isInteger from this library has the mentioned type guard, number is number.

To summarize the problem: When Number.isInteger returns false, the passed value may still be a number (just not an integer).

Unfortunately as that discussion shows there is currently no good way to represent this kind of 'weak type guard' in TypeScript, although https://github.com/microsoft/TypeScript/issues/15048#issuecomment-1174371299 does present an interesting workaround.

Adapted from that comment, you could do something like the following:

declare const container: unique symbol;
type SubtypeOf<T> = T & { [container]: T };

interface NumberConstructor {
  isInteger<T>(number: T): number is number & SubtypeOf<T>;
}

function test(n: unknown) {
  if (typeof n === "string" || typeof n === "number") {
    const a = n; // string | number

    if (Number.isInteger(n)) {
      const b: number = n; // number & { [container]: string | number }
    } else {
      const e = n; // string | number
    }
  }
}

Although the truthy branch has an awkward type for n, the assignment to b is valid (and the falsy branch maintains the unconstrained type).

I don't know what the best solution is here - remove the type guard, apply the workaround, or leave the type guard in place. The current type guard is technically unsafe though.

ehoogeveen-medweb avatar Jul 04 '22 22:07 ehoogeveen-medweb