Home > database >  Explanation of " R['length'] extends N " syntax in TypeScript
Explanation of " R['length'] extends N " syntax in TypeScript

Time:05-05

new to TypeScript, was searching for a way to define a fixed size array and came across this:

type Tuple<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

type Tuple9<T> = Tuple<T, 9>;
type Board9x9<P> = Tuple9<Tuple9<P>>;

...which obtains the desired result using recursive conditional types. I was trying to understand the syntax that is being used, and I couldn't really understand what is happening behind the scenes particularly in this point: R['length'] extends N.

I understood that it checks if the array is of the desired length and if not it'll recursively call _TupleOf with an extra element until that condition becomes true. but why is that happening? I searched for answers but couldn't really find anything useful.

Does R['length'] return the actual length as a number if it is applied to a type? And why extends seems to behaves like a == operator in this case?

I've also tried double-checking if it behaves as i understood in this playground (you can hover over b and c to check the types) and it seems it does, still can't understand why tho.

Any link or explanation? thanks in advance!

CodePudding user response:

It's because:

  1. The type of the length property of a tuple is a literal numeric type equal to the number of elements in the tuple. So not just a number, but a specific number (like 42).
  2. Numeric literal types extend number. (Hmmm... I'm not sure why I mentioned this, but it's in the examples below so I'll keep it.)
  3. A numeric literal type only "extends" (is the same as or a subtype of) itself.
type TwoThings = [string, string];
type TwoThingsLength = TwoThings["length"];
//   ^? −−− type TwoThingsLength = 2

type IsNumber<T> = T extends number ? true : false;
type A = IsNumber<27>;
//   ^? −−− type A = true
type B = IsNumber<"example">;
//   ^? −−− type B = false

type IsThisNumber<T, Num> = T extends Num ? true : false;
type C = IsThisNumber<27, 27>;
//   ^? −−− type C = true
type D = IsThisNumber<27, 42>;
//   ^? −−− type D = false

// Which leads us to
type E = IsThisNumber<TwoThingsLength, 2>; // true, they're the same literal number
//   ^? −−− type E = true
type F = IsThisNumber<TwoThingsLength, 3>; // false, they aren't
//   ^? −−− type F = false

Playground link

So R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]> works by:

  • If R['length'] is the same as the target length N, evaluate to the R type itself
  • If not, create a new tuple type which has element T plus all of the previous elements (...R) — e.g., add one — and recurse
  • Related