Search code examples
typescripttypesnaninfinityconditional-types

How to use conditional types on NaN and Infinity


I am creating a function in TypeScript that essentially extends the functionality of the typeof keyword, including showing the appropriate IntelliSense for the type whenever it is known (via conditional types). I have it working just fine for all my other custom types so they only show IntelliSense for 1 return type when it is known, but I am not sure how to make it work specifically for the NaN and Infinity values. I believe this is because these are being overlooked as "number" literal types. (working example below)

public static async type(value: number): Promise<'number' | 'nan' | 'infinity'> // would rather this only show just 1 type
public static async type(value: []): Promise<'array'>
public static async type(value: Promise<any>): Promise<'promise'>
// and several more...
public static async type(value: any): Promise<T_detectType> {
   // do stuff
}

My Question: Is it possible to create a conditional type based on NaN and/or Infinity? (non-working example below)

public static async type(value: NaN): Promise<'nan'>
public static async type(value: Number.POSITIVE_INFINITY | Number.NEGATIVE_INFINITY): Promise<'infinity'>

I am able to give more details as necessary, but I am unsure what you may need and I am trying to keep this question concise.


Solution

  • For better or worse, TypeScript does not have literal types representing NaN, Infinity, or -Infinity. The narrowest type available for NaN, Infinity, and -Infinity is just number. So, for example, while a const initialized with a numeric literal will generally have a literal type:

    const x = 1.234;
    // const x: 1.234
    

    The same does not happen with NaN or Infinity:

    const y = NaN;
    // const y: number
    const z = -Infinity;
    // const z: number
    

    There's no workaround I can think of for this that isn't significantly worse than just giving up. You could imagine providing some simulated nominal type

    type NaN = number & { NaN: true };
    type Infinity = number & { Infinity: true };
    type NegInfinity = number & { NegInfinity: true };
    

    But then you'd need to use type assertions to use them:

    const w = NaN as NaN;
    const v = Infinity as Infinity;
    const u = -Infinity as NegInfinity;
    

    and if your goal is to write Blah.type(NaN) and have Promise<'nan'> come out, you could only achieve that goal by writing Blah.type(NaN as NaN) which is silly, especially because nothing stops someone from writing Blah.type(123 as NaN).

    In TypeScript, NaN, Infinity, and -Infinity are just of type number, and that's that.


    As for changing this:

    There is a declined feature request at microsoft/TypeScript#15135 asking for such support. From reading that issue (and the linked design discussion at ms/TS#15356) it looks like nobody was able to articulate an important enough use case to support it. Supporting NaN as a literal might be annoying because, for example, NaN === NaN is false.

    Personally, I'm inclined to agree that we care about NaN types because they are falsy, and tracking falsiness is something worthwhile. But in the pull request implementing numeric literal types, a comment by the implementer says that falsiness doesn't matter, and he can't think of any meaningful "scenarios where you'd use NaN and Infinity as singleton types, discriminant values, or literal values for overloading."

    There is an open issue at microsoft/TypeScript#32277 asking for Infinity and -Infinity literal types as opposed to NaN; it's currently labeled as "awaiting more feedback" which means they want to see comments describing compelling use cases before considering implementing it.

    And there's an issue at microsoft/TypeScript#36964 that demonstrates how the lack of consideration of a NaN-like type causes incorrect narrowings... a falsy number is assumed to be 0 whereas it should really be 0 | typeof NaN to be correct. It's also labeled as awaiting feedback, and the requester seems to be saying that it should be number since typeof NaN is number (as opposed to saying that there should be a NaN type). But it's relevant.

    It's just about conceivable that enough people could add comments to those two open issues saying how literal NaN, Infinity, and -Infinity types should be added, and detailing use cases that are persuasive enough to eventually cause this to happen. But it's quite likely that this will not happen. And it seems to me very unlikely that stronger typing of a typeof helper function is going to be seen as particularly compelling. So while it might not hurt anything to go there and give them a 👍 and describe what you're trying to do, I wouldn't count on it having any effect.

    For now, I think you should probably just give up on trying to distinguish NaN, Infinity, and -Infinity from number, unfortunately.

    Playground link to code