Is there a way to represent a non-negative integer in TypeScript so that the compiler would prevent using fractions and negatives?

Update 2021:

Yes, template literals allow this to be done; observe:

type NonNegativeInteger<T extends number> =
    number extends T 
        ? never 
        : `${T}` extends `-${string}` | `${string}.${string}`
            ? never 
            : T;

Note that number extends T is necessary to restrict a general number type.

Usage:

function negate<N extends number>(n: NonNegativeInteger<N>): number {
    return -n;
}


negate(3); // success
negate(3.1); // failure
negate(-3); // failure
negate(-3.1); // failure

Usage

In response to @ianstarz comment:

How would you use this on a class field or variable type? myField: NonNegativeInteger = 42 doesn’t seem to work—I’m not sure what to pass in as the generic type in this case. Can you also provide an example of how to use the generic in this case?

Understand that in Typescript, literals are considered types unto themselves; example: 10 is assignable to some let x: number, but only 10 is assignable to some let x: 10. Furthermore, Typescript’s has a powerful type-inference system, but it can only go so far before becoming a burden to develop with. The goal of the above type is to do one of two things:

  1. Limit literal arguments of a function.
  2. Apply further type manipulation.

Your question doesn’t just apply to class fields, nor the above type. Typescript variables apply type inference at the time of declaration, not assignment; this inference does not extend to Generics on variables.

To demonstrate the difference between generic variable types and function calls, consider the error below when using a generic identity type:

type Identity<T> = Identity;

// Generic type 'Example' requires 1 type argument(s)
let x: Identity = 10;

Compared to:

type Identity<T> = Identity;

function identity<T>(x: Identity<T>): T {
    return x;
}

let y = identity(10); // Success, y has type `number`
const z = identity(10); // Success, z has type `10`

Note how z has assumed a literal type. In fact, we could explicitly type y the same, but it would only allow 10 as a value, not any other number.

Finite Literal Unions

If you had a finite amount of integer values, like file descriptors, make a field with a type like the following:

type EvenDigit = 0 | 2 | 4 | 6 | 8;

let x: EvenDigit = 2; // Success
let y: EvenDigit = 10; // Failure

If you’re crazy, write a script that generates the union types. Note there is likely a version specific cap on the amount of members for a union type.

Calculated Literal Union

If you wanted to go SUPER meta something like this would generate a range of types:

// Assumes, for simplicity, that arguments Start and End are integers, and
// 0 < Start < End.
// Examples:
// Range<0, 5> -> 0 | 1 | 2 | 3 | 4 | 5
// Only can calculate so much:
// Range<0, 100> -> 'Type instantiation is excessively deep and possibly infinite.ts(2589)'
// Tail end recursion being introduced in Typescript 4.5 may improve this.
type Range<Start extends number, End extends number> = RangeImpl<Start, End>;
type RangeImpl<
    Start extends number,
    End extends number,
    T extends void[] = Tuple<void, Start>
> = End extends T["length"]
    ? End
    : T["length"] | RangeImpl<Start, End, [void, ...T]>;

// Helper type for creating `N` length tuples. Assumes `N` is an integer
// greater than `0`. Example:
// Tuple<number, 2 | 4> -> [number, number] | [number, number, number, number]
type Tuple<T, N extends number> = TupleImpl<T, N>;
// prettier-ignore
type TupleImpl<T, N extends number, U extends T[] = []> =
    N extends U["length"]
        ? U
        : TupleImpl<T, N, [T, ...U]>;

Generic Assignment Method

You can create a class with assignment and retriever methods (not a getter/setter pair because An accessor cannot have type parameters ts(1094) ).

Example:

class MyClass {
    private _n: number = 42;
    
    // infers return type `number`
    getN() {
        return this._n;
    }

    setN<T>(n: NonNegativeInteger<T>) {
        // Optionally error check:
        if (Number.isInteger(n) || n <= 0) {
            throw new Error();
        }
        this._n = value;
    }
}

Leave a Comment

deneme bonusu veren sitelerbahis casinomakrobetceltabetpinbahispolobetpolobet girişpinbahis girişmakrobet girişpulibet girişmobilbahis girişkolaybet giriş