207

Since typescript 2.0 RC (or even beta?) it is possible to use number literal types, as in type t = 1 | 2;. Is it possible to restrict a type to a number range, e.g. 0-255, without writing out 256 numbers in the type?

In my case, a library accepts color values for a palette from 0-255, and I'd prefer to only name a few but restrict it to 0-255:

const enum paletteColor {
  someColor = 25,
  someOtherColor = 133
}
declare function libraryFunc(color: paletteColor | 0-255); //would need to use 0|1|2|...
ASDFGerte
  • 4,695
  • 6
  • 16
  • 33
  • 1
    Note: enums define a set of named **numeric** constants, not a new type. Therefor declaring that numbers can be passed instead of `paletteColor`s is unnecessary. – Burt_Harris Sep 14 '16 at 23:33
  • 3
    @Burt_Harris true. A way to restrict an enum to 0-255 would be needed as well. One could also just use an indexer object instead of an enum, although slightly ugly. The `|` is not needed either way, it should in the best scenario simply be `paletteColor` if it were restricted to 0-255, or just 0-255. – ASDFGerte Sep 15 '16 at 00:26
  • 5
    Note: As of TS 2.4, string literals are now allowed as enum values https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/#string-enums – Kevin Suttle Sep 11 '17 at 18:49
  • FYI, what you are asking for is called "dependent types" and no this feature does not exist in TS. Some languages that do have this feature are Agda, Idris, Coq. – Nick Zalutskiy Nov 27 '18 at 18:50
  • Check my [article](https://catchts.com/range-numbers#part_2) and [answer](https://stackoverflow.com/questions/75108082/is-it-possible-to-generate-a-union-type-of-numbers-without-explicitly-stating-ea/75109244#75109244) – captain-yossarian from Ukraine Jan 14 '23 at 14:47

16 Answers16

120

Edit: this is an old answer. TS >= 4.5 now has tools to deal with this, although it may or may not be limited for your use case. For smallish ranges, this answer works:

type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
  ? Acc[number]
  : Enumerate<N, [...Acc, Acc['length']]>

type IntRange<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>

type T = IntRange<20, 300>

---- Old answer ----

No it's not possible. That kind of precise type constraint is not available in typescript (yet?)

Only runtime checks/assertions can achieve that :(

AlexG
  • 3,617
  • 1
  • 23
  • 19
  • 2
    With TypeScript 4.5.0 it will be possible. See the second playground link: https://stackoverflow.com/a/69090186/2158015 – Didii Oct 25 '21 at 09:46
  • 1
    Not going to downvote because this is an old answer, but update it, please. – Jorge Fuentes González Sep 03 '22 at 05:32
  • This doesn't quite work as expected. `let x: IntRange<0, 2> = 0 // ok`, `let x: IntRange<0, 2> = 1 // ok`, `let x: IntRange<0, 2> = 2 // errors` See this comment on the original linked answer https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range/70307091#comment127925458_70307091 – buggedcom May 23 '23 at 16:08
  • awesome answer thanx – cantaş Jun 11 '23 at 06:46
88

If You have small range, you can always write something like:

type MyRange = 5|6|7|8|9|10

let myVar:MyRange = 4; // oops, error :)

Of course it works just for integers and is ugly as hell :)

Adam Szmyd
  • 2,765
  • 2
  • 24
  • 32
57

Yes, it's possible BUT:

The 1st. Solution Will be a dirty Solution The 2nd. Solution Will be partial (from x to y where y is a small number, 43 in my case) The 3rd. Solution will be a Complete solution but really advance with Transformers, Decorators, etc.

1. Dirty solution ( the easiest and fast way first ) using @Adam-Szmyd solution:

type RangeType = 1 | 2 | 3

if you need an extensive range, just print and copy/paste:

// Easiest just incremental
let range = (max) => Array.from(Array(max).keys()).join(" | ");
console.log('Incremental')
console.log(range(20))
// With range and steps
let rangeS = (( min, max, step) => Array.from( new Array( max > min ? Math.ceil((max - min)/step) : Math.ceil((min - max)/step) ), ( x, i ) => max > min ? i*step + min : min - i*step ).join(" | "));
console.log('With range and steps')
console.log(rangeS(3,10,2))

You may be tented of doing things like this

const data = [1, 2, 4, 5, 6, 7] as const;
type P = typeof data[number];

showing that P has the enumerated types

but instead using functions

const rangeType20 = Array.from(Array(20).keys()) as const;

showing that can't be done with functions

But at the moment this doesn't work, only work if is a literal. Even the error is not quite correct.

2. Partial solution (source) Partial solution

type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;

type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];

export type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;

export type Range<FROM extends number, TO extends number> = Exclude<Enumerate<TO>, Enumerate<FROM>>;

type E1 = Enumerate<43>;

type E2 = Enumerate<10>;

type R1 = Range<0, 5>;

type R2 = Range<0, 43>;

3. Complete solution but really advance with Transformers, Decorators, etc.

Using the functions on the first solution, you could replace at compiletime by the values, using transformer. Similarly, but on runtime using decorators.

titusfx
  • 1,896
  • 27
  • 36
46

It is possible with Typescript 4.5 to do a tail-recursion elimination on conditional types.

type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
  ? Acc[number]
  : Enumerate<N, [...Acc, Acc['length']]>

type Range<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>

type T = Range<20, 100>
  • 2
    Indeed, tail recursion elimination allows for drastically more recursive calls than the 41 that other solutions have, although not fully eliminating it (e.g. `Rangee<10, 1000>` still fails). PS: wouldn't `type ArrayToUnion = T extends (infer U)[] ? U : never;` be the same, but simpler? – ASDFGerte Dec 10 '21 at 16:14
  • 1
    In fact, we can do even simpler `type TupleToUnion = T[number]`. See [Tuple to Union](https://github.com/type-challenges/type-challenges/blob/master/questions/10-medium-tuple-to-union/README.md). – Guillaume Mastio Dec 11 '21 at 13:02
  • @ASDFGerte Could you elaborate how you'd use `ArrayToUnion` or `TupleToUnion` in this scenario? – Andru Mar 23 '22 at 09:58
  • @Andru it was a comment towards an older version of this answer, see the edit history. – ASDFGerte Mar 23 '22 at 13:41
  • 1
    @Andru More random side-facts: while shorter than the original version, i had initially memorized the `ArrayToUnion` i wrote from [another question](https://stackoverflow.com/questions/41253310/typescript-retrieve-element-type-information-from-array-type), which however was edited to use that instead of `T[number]` for invalid reasons (see retsam's comment over there, i had asked in the TS discord about it, as i couldn't see a difference). The alternative of `T[number]` is already mentioned in the following comment here, so i didn't see a reason to elaborate too much. – ASDFGerte Mar 23 '22 at 13:51
  • 8
    That is the best answer, just add `| T` like `Exclude, Enumerate> | T` to make this work `const range : Range<0, 100> = 100;` – Henry Ruhs May 28 '22 at 09:25
  • @HenryRuhs, thank you, I was looking for a way to make it include the max-number. I couldn't figure it out. I was adding `+ 1` in places, hoping that that would work. But that's apparently not valid TypeScript. So again, thank you. You're solution is so obvious now that I see it. – Evert Nov 23 '22 at 20:10
  • @GuillaumeMastio, would you happen to know if it's possible to add an extra generic argument that represents the step size? Now it's 1 by default, but I'd love to be able to write `type T = Range<0, 1, 0.1>` and get the numbers 0, 0.1, 0.2 ... 0.9. – Evert Nov 23 '22 at 20:16
  • Seems too complicated for how it would be used. IMO post-typing validations would be more beneficial. – Elijah Mock Aug 11 '23 at 01:00
33

It's not possible for the moment but there's an open issue on GitHub. Currently they are still waiting for a proposal but this functionality might come someday.

In short you won't be able to use a range of numbers as a type until a proposal comes out.


Update - August 2021

A proposal exists now. For more details, see Interval Types / Inequality Types.

Elie G.
  • 1,514
  • 1
  • 22
  • 38
  • 4
    The concrete proposal is here now: [Interval Types / Inequality Types](https://github.com/microsoft/TypeScript/issues/43505) – SWdV Aug 14 '21 at 17:00
  • Can you please explain to me, how i can use this solution in my react element prop interface for example. – Ningaro Apr 10 '22 at 18:10
  • 1
    @Ningaro It's just a proposal for now which means it's not a solution yet. Interval types won't be available until the proposal gets implemented in TypeScript. In the meantime, you can use one of the alternatives or work around described by other answers. – Elie G. Apr 13 '22 at 14:14
18

Update 1

Since typescript v4.5 add tail recursive evaluation of conditional types. Issue Link

Now the maximum number can be 998. It's totally enough for your question.

Playground Link

type Ran<T extends number> = number extends T ? number :_Range<T, []>;
type _Range<T extends number, R extends unknown[]> = R['length'] extends T ? R[number] : _Range<T, [R['length'], ...R]>;

type R5 = Ran<998>
const a: R5 = 3 // correct
const b: R5 = 999 // wrong

Origin Answer

It is possible now with Typescript 4.1 Recursive Conditional Types

type Range<T extends number> = number extends T ? number :_Range<T, []>;
type _Range<T extends number, R extends unknown[]> = R['length'] extends T ? R['length'] : R['length'] | _Range<T, [T, ...R]>;

type R5 = Range<5>
const a: R5 = 3 // correct
const b: R5 = 8 // error. TS2322: Type '8' is not assignable to type '0 | 1 | 2 | 3 | 4 | 5'.

But unfortunately, if your length is too long, the recursive type will fail

type R23 = Range<23>
// TS2589: Type instantiation is excessively deep and possibly infinite.

Well, it works but not really works. :)

Dean Xu
  • 4,438
  • 1
  • 17
  • 44
  • [Playground Link](https://www.typescriptlang.org/play?jsx=0&allowSyntheticDefaultImports=true#code/LYewJgrgNgpgBAFxgZwXA3gKDjxBPAB3gCUBDAOwHMYAeAFThgA8lyxk5yJgAjGAJwB8cALyduffoxYw2HBgH5xvAXABccAPpkqtOgBo4AbQC6ggNzZcCQvG0Vq9aa3bLJh4s9muI5ANbkIADu5KbCYsRGAOSwVAgAFlEmXnJwinCRMbKUCUnqGdGxOYnJAD5aOo4GxtUAdPXEZpZWODZEGQCsohkOtB2CLXAAxiDkqHCkGsRdYgDMcAD0C8Mg-PwwQwiDI2NoPFMzcAAci8sC-Ku1aQDKAEyzt7cadLZwUUdRcACWHIFopMhkF9KORSDxYIgQPh2lEAAxwcoARgRcFuKPm5QALCiOlFapgAL5AA) – Dean Xu Aug 23 '21 at 08:47
15

While not the best solution (since some checking will be handled at runtime), it's worth mentioning that "opaque types" can help enforce that you're inputting the expected values.

Here's a example:

type RGBColor = number & {_type_: "RGBColor"};

const rgb = (value: number): RGBColor => {
  if (value < 0 || value > 255) {
    throw new Error(`The value ${value} is not a valid color`);
  }

  return value as RGBColor;
};

// Compiler errors
const color1: RGBColor = 200; // fail - number is not RGBColor
const color2: RGBColor = 300; // fail - number is not RGBColor

// Runtime error
const color3: RGBColor = rgb(300); // fail - The value 300 is not a valid color

// Pass
const color4: RGBColor = rgb(100);
const color5: RGBColor = rgb(255);
Grafluxe
  • 483
  • 4
  • 11
5

With validation range numbers (positive and integer range) ts 4.6.3

type IsPositive<N extends number> = `${N}` extends `-${string}` ? false : true;

type IsInteger<N extends number> = `${N}` extends `${string}.${string}`
  ? never
  : `${N}` extends `-${string}.${string}`
  ? never
  : number;

type IsValid<N extends number> = IsPositive<N> extends true
  ? IsInteger<N> extends number
    ? number
    : never
  : never;

type PositiveNumber<
  N extends number,
  T extends number[] = []
> = T["length"] extends N ? T[number] : PositiveNumber<N, [...T, T["length"]]>;

type Range<N1 extends IsValid<N1>, N2 extends IsValid<N2>> = Exclude<
  PositiveNumber<N2>,
  PositiveNumber<N1>
>;
type RangeType = Range<1, 5>;

And here with negative range but with some constraints. Range are literals. I don't why but i couldn't to get not literaly negative numbers. Maybe someone knows warkaround

type IsInteger<N extends number> = `${N}` extends `${string}.${string}`
  ? never
  : `${N}` extends `-${string}.${string}`
  ? never
  : number;

type NegativeLiteralNumbers<
  N extends number,
  T extends string[] = []
> = `${N}` extends `-${string}`
  ? `-${T["length"]}` extends `${N}`
    ? T[number]
    : NegativeLiteralNumbers<N, [...T, `-${T["length"]}`]>
  : never;

type PositiveLiteralNumber<
  N extends number,
  T extends string[] = []
> = `${N}` extends `${string}`
  ? T["length"] extends N
    ? T[number]
    : PositiveLiteralNumber<N, [...T, `${T["length"]}`]>
  : never;

type RangeLiteralNegative<F extends number, T extends number> = Exclude<
  NegativeLiteralNumbers<F>,
  NegativeLiteralNumbers<T>
>;
type RangeLiteralPositive<F extends number, T extends number> = Exclude<
  PositiveLiteralNumber<T>,
  PositiveLiteralNumber<F>
>;
type RangeLiteral<N1 extends IsInteger<N1>, N2 extends IsInteger<N2>> =
  | (`${N1}` extends `-${string}`
      ? RangeLiteralNegative<N1, 0>
      : `${N1}` extends `${string}`
      ? RangeLiteralPositive<0, N1>
      : never)
  | (`${N2}` extends `-${string}`
      ? RangeLiteralNegative<N2, 0>
      : `${N2}` extends `${string}`
      ? RangeLiteralPositive<0, N2>
      : never);

type RangeLiteralType = RangeLiteral<-5, 3>;
udarrr
  • 83
  • 1
  • 4
3

Is it possible to restrict a type to a number range, e.g. 0-255, without writing out 256 numbers in the type?

Not posible until now, but you can make a lifehack, and generate desired sequence with one line of code and copy/paste result

new Array(256).fill(0).map((_, i) => i).join(" | ")

Profesor08
  • 1,181
  • 1
  • 13
  • 20
3

EDIT: Ahh I did not read the provided answers carefully enough! @titusfx already provided this answer in another form. As with his approach this is limited in respect to the amount of numbers you can generate. This is not an actual solution but a workaround which works in a very limited range of numbers!

Original answer:

There is a workaround to this. Borrowing from the answer https://stackoverflow.com/a/52490977 (which limits this solution to TypeScript v 4.1 and higher):

type _NumbersFrom0ToN<
    Nr extends number
    > =
    Nr extends Nr ?
        number extends Nr ?
            number :
            Nr extends 0 ?
                never :
                _NumbersFrom0ToNRec<Nr, [], 0> :
        never;

type _NumbersFrom0ToNRec<
    Nr extends number,
    Counter extends any[],
    Accumulator extends number
    > =
    Counter['length'] extends Nr ?
        Accumulator :
        _NumbersFrom0ToNRec<Nr, [any, ...Counter], Accumulator | Counter['length']>;

type NrRange<
    Start extends number,
    End extends number
    > =
    Exclude<_NumbersFrom0ToN<End>, _NumbersFrom0ToN<Start>>;

let nrRange: NrRange<14, 20>;

range type

Which creates the type 14 | 15 | 16 | 17 | 18 | 19. To make this work we just need to leverage the feature that TypeScript can count via the length attribute of the new improved tuple type inspections. So we just extend an array as long as the length of the array is not the same as the input number. While we extend the array we remember the lengths which we already visited. This in turn results in a counter with extra steps.

EDIT: I put those types in a package for an easy reausability: https://www.npmjs.com/package/ts-number-range

Feirell
  • 719
  • 9
  • 26
  • 1
    This is interesting, but sadly, the way this works is extremely complex for the compiler. E.g. `NrRange<0, 42>` will already give "Type instantiation is excessively deep and possibly infinite.(2589)", for more or less obvious reasons. It's useful for the edge case, where a range is so long, that typing it manually isn't useful, but still "reasonable in size for this approach". – ASDFGerte Mar 19 '21 at 14:28
  • @ASDFGerte good point, I did not test it with bigger numbers. But you are right there needs to be an integrated type which can handle this more gracefully :) But there is another case besides comfort. When you dont know the bound and the user provides the length. Then you still can restrict him to that range or it just collapses :D. In any case, this is not ideal but better than nothing. – Feirell Mar 19 '21 at 15:03
  • @ASDFGerte I added a disclaimer to the packge readme. Thank you for the hint! – Feirell Mar 19 '21 at 15:17
3

my solution with tail recursion

type BuildArray<
    Length extends number, 
    Ele = unknown, 
    Arr extends unknown[] = []
> = Arr['length'] extends Length 
        ? Arr 
        : BuildArray<Length, Ele, [...Arr, Ele]>;

type Add<Num1 extends number, Num2 extends number> =  [...BuildArray<Num1>,...BuildArray<Num2>]['length'];
type Subtract<Num1 extends number, Num2 extends number> = BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest] ? Rest['length'] : never;

type _RangeOf<start extends number, end extends number, R extends unknown[] = [start]> = R['length'] extends Subtract<end, start> ? [...R, end][number] : _RangeOf<start, end, [...R, Add<start, R['length']> ]> ;

type myRange = _RangeOf<2, 7>; // 2, 3, 4, 5, 6, 7
const myRange: myRange = 7; 
const myRange2: myRange = 1; // error
const myRange2: myRange = 8; // error

Try here online

tsu
  • 1,122
  • 1
  • 10
  • 22
2

With this method it can be possible to compare a type number range of 1,000,000 (possibly more)

I have expanded on this answer to add 3 more features:

1 - A condition that if you enter the same number twise then that number is returned

2 - The second number entered is also included in the range type map

3 - If used in certain way we can allow for typing number ranges between 0 - 89,999 (or even more)

The first 2 features are represented in this code:

type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
  ? Acc[number]
  : Enumerate<N, [...Acc, Acc['length']]>

type IntRange<F extends number, T extends number> = F extends T ? 
  F : 
  Exclude<Enumerate<T>, Enumerate<F>> extends never ? 
    never : 
    Exclude<Enumerate<T>, Enumerate<F>> | T

type G = IntRange<5,5>
// type G = 5
type T = IntRange<0, 9>
// type T = 0 | 9 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8

Here's where things get interesting. Proceed only if you are happy with the end type being a union of string literals..

First we rewrite the name of "IntRange" and our "G" Range type variable names. Then make the renamed 'myRange' type a fixed value of multiple command substitutions:

type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
  ? Acc[number]
  : Enumerate<N, [...Acc, Acc['length']]>

type T<F extends number, T extends number> = F extends T ? 
  F : 
  Exclude<Enumerate<T>, Enumerate<F>> extends never ? 
    never : 
    Exclude<Enumerate<T>, Enumerate<F>> | T

type myRange = `${T<0, 8>}${T<0, 9>}${T<0, 9>}${T<0, 9>}${T<0, 9>}`

Here myRange is a union of string literals between 00,000 - 89,999 (any number string between these values with work, any outside will not)

We can start doing some more funky stuff if we abstract all of this in a tidy little interface:

interface R<N extends number = 0, T extends number = 0, Acc extends number[] = []> {
    O: {
        H: Acc['length'] extends N
        ? Acc[number]
        : R<N, 0, [...Acc, Acc['length']]>["O"]["H"]
    },
    T: N extends T ? N : Exclude<R<T>["O"]["H"], R<N>["O"]["H"]> extends never ? never : Exclude<R<T>["O"]["H"], R<N>["O"]["H"]> | T ,
    K: {
        XX1 : `${R<0,8>["T"]}${R<0,9>["T"]}${R<0,9>["T"]}${R<0,9>["T"]}`,
    }
    U: R["K"][keyof R["K"]]

}

type myRange = R["U"]

Here myRange is also a union of string literals between 00,000 - 89,999 However we can keep adding properties to the R.K object and the value of those keys will be added to the union. With this we can easily increase the number range from 89,999 to 99,999 like this:

interface R<N extends number = 0, T extends number = 0, Acc extends number[] = []> {
    O: {
        H: Acc['length'] extends N
        ? Acc[number]
        : R<N, 0, [...Acc, Acc['length']]>["O"]["H"]
    },
    T: N extends T ? N : Exclude<R<T>["O"]["H"], R<N>["O"]["H"]> extends never ? never : Exclude<R<T>["O"]["H"], R<N>["O"]["H"]> | T ,
    K: {
        XX1 : `${R<0, 8>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        XX2 : `${R<9, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`
    }
    U: R["K"][keyof R["K"]]

}

type myRange = R["U"]

The limit of any one string literal range type seems to be 89,999. If you go over this it still compiles the type however the type becomes 'any'. When you chain them and merge the union types like above however it appears this limit can be bypassed.

I haven't tested the limit of this but here's one for 000,000 - 999,999. Although its starting to make a noticeable impact on the performance of the type compiler..

The Compiler makes it look like the limit is 888,888 but it is 999,999 TS Fiddle

interface R<N extends number = 0, T extends number = 0, Acc extends number[] = []> {
    O: {
        H: Acc['length'] extends N
        ? Acc[number]
        : R<N, 0, [...Acc, Acc['length']]>["O"]["H"]
    },
    T: N extends T ? N : Exclude<R<T>["O"]["H"], R<N>["O"]["H"]> extends never ? never : Exclude<R<T>["O"]["H"], R<N>["O"]["H"]> | T ,
    K: {
        xx1 : `${R<0, 1>["T"]}${R<0, 3>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        xx2 : `${R<0, 1>["T"]}${R<4, 7>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        XX3 : `${R<0, 1>["T"]}${R<8, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,

        XX4 : `${R<2, 3>["T"]}${R<0, 3>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        XX5 : `${R<2, 3>["T"]}${R<4, 7>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        XX6 : `${R<2, 3>["T"]}${R<8, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,

        XX7 : `${R<4, 5>["T"]}${R<0, 3>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        XX8 : `${R<4, 5>["T"]}${R<4, 7>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        XX9 : `${R<4, 5>["T"]}${R<8, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,

        X10 : `${R<6, 7>["T"]}${R<0, 3>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        X11 : `${R<6, 7>["T"]}${R<4, 7>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        X12 : `${R<6, 7>["T"]}${R<8, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,

        X13 : `${R<8, 9>["T"]}${R<0, 3>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        X14 : `${R<8, 9>["T"]}${R<4, 7>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
        X15 : `${R<8, 9>["T"]}${R<8, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}${R<0, 9>["T"]}`,
    }
    U: R["K"][keyof R["K"]]

}

type myRange = R["U"]

var myString: myRange = "999999"
1

I have a better suggestion.

type NumericRange<
    START extends number,
    END extends number,
    ARR extends unknown[] = [],
    ACC extends number = never
> = ARR['length'] extends END
    ? ACC | START | END
    : NumericRange<START, END, [...ARR, 1], ARR[START] extends undefined ? ACC : ACC | ARR['length']>
0

Not using static type-checking, only at runtime for example using a library like io-ts where you could use taggedUnion for instance: https://github.com/gcanti/io-ts/issues/313

GibboK
  • 71,848
  • 143
  • 435
  • 658
0
type IncrementMap = {
  0: 1;
  1: 2;
  2: 3;
  // ...
  1233: 1234;
  1234: 1234; // To make all values also Incrementable
};

type Incrementable = keyof IncrementMap;
type Increment<N extends Incrementable> = IncrementMap[N];

type Range<
  F extends Incrementable,
  T extends Incrementable,
  A extends number[] = [],
> = F extends T ? A[number] | F : Range<Increment<F>, T, [...A, F]>;

let x: Range<1, 10> = 1;
x = 0; // Error
x = 11; // Error

The list of numbers for the map can be generated here.

How does this compare to the other solutions? The obvious downside is the presence of IncrementMap, which I mitigate by keeping it in its own file. The unique upside, however, is that there's no ceiling. Each range is still limited to 999 numbers, but they can be composed into a bigger range:

type Foo = Range<0, 998> | Range<999, 1997>;

Tested up to 10k, but I think the only limit is the amount of properties TypeScript permits to have in an object (not sure if there is one, maybe the only limit is performance). It also works for bigger numbers, e.g. 100k, just need to make sure they're in the map (no need to go all the way from 0, just in case). Negative numbers work as well, of course.

This would be much nicer with literal type math though.

Alec Mev
  • 4,663
  • 4
  • 30
  • 44
-5

This worked for me, to constrain the height of an html textarea. It clips a test value to the range 5...20.

const rows = Math.min(Math.max(stringArray.length, 5), 20);
mosaic
  • 93
  • 1
  • 3