Normally, if you want to do addition in TypeScript, you can do it just using "+" operator.
index.ts1const a = 1 + 2; // 3
You can run this code using some tools like ts-node, and you can get the result of 3 in runtime.
But, what if you want to do arithmetic in compile time?
1type Add<A extends number, B extends number> = A + B;
The above code is not valid. It will cause a compile error. A and B is just a type, not a value, "+" operator is not defined for them.
Tuple Type in TypeScript
In fact, you can do addition in compile time using tuple type. But why?
Tuple type is kind of an array type, but it has fixed length and position. Fixed position means compiler knows which type is in which position in build time.
1type StringNumberPair = [string, number];
For example, the above StringNumberPair type is a tuple which has two elements, first element is string, second element is number. And, you can get the length of tuple type using length property, this is an interesting feature.
1// StringNumberPair["length"] is same as literal type 22const pairLength1: StringNumberPair["length"] = 2;3const pairLength2: 2 = 2;
Use this feature for type-level addition.
1. type-level value
As I mentioned, "+" operator is not defined for number type, so normal number type is not suitable for type-level addition. I will define a new type.
1type Value<NUM extends number, LOOP extends number[] = []> = LOOP["length"] extends NUM ? LOOP : Value<NUM, [...LOOP, 0]>
Value type represents number but its format is tuple type. For example,
- Value<3> is [0, 0, 0]
- Value<5> is [0, 0, 0, 0, 0]
In this context, the elements of tuple type is not important, the important thing is the length of tuple type.
2. type-level addition
Tuple type can be spread using ... operator, so you can get the type-level addition type Add as the following.
1type Add<A extends number, B extends number> = [...Value<A>, ...Value<B>]["length"];
This code works correctly.
1type Ex1 = Add<3, 5> // 82type Ex2 = Add<100, 100> // 2003type Ex3 = Add<123, 456> // 579
OK, try 2000 + 24.
1type Ex4 = Add<2000, 24>
This code does not work with the following error message.
Type instantiation is excessively deep and possibly infinite.
This error is thrown by a TypeScript compiler. According to checker.ts,
checker.ts:L186561// We loop here for an immediately nested conditional type in the false position, effectively treating2// types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for3// purposes of resolution. We also loop here when resolution of a conditional type ends in resolution of4// another (or, through recursion, possibly the same) conditional type. In the potentially tail-recursive5// cases we increment the tail recursion counter and stop after 1000 iterations.6while (true) {7 if (tailCount === 1000) {8 error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);9 return errorType;10 }1112...
checker.ts:L199111if (instantiationDepth === 100 || instantiationCount >= 5000000) {2 // We have reached 100 recursive type instantiations, or 5M type instantiations caused by the same statement3 // or expression. There is a very high likelyhood we're dealing with a combination of infinite generic types4 // that perpetually generate new type identities, so we stop the recursion here by yielding the error type.5 tracing?.instant(tracing.Phase.CheckTypes, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount });6 error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);7 return errorType;8}
There are 2 positions can throw this error. In this case, the former code is executed. I created Value type using recursion and conditional type. Each recursion increments the length of tuple type by 1. To create Value<2000>, the length of tuple type is 2000, the recursion is executed 2000 times.
Simply,
1type V1 = Value<999> // OK2type V2 = Value<1000> // NG. Type instantiation is excessively deep and possibly infinite.
There is limitation.
Type-Level Adder
We found there are some limitations for type manipulation. To solve this problem, I created a library called typed-circuit
In this library, Half Adder and Full Adder are implemented. There are some restriction, but 16bit addition is available.
1type BitRepr_6 = [HalfByte0, HalfByte0, HalfByte0, [0, 1, 1, 0]];2type BitRepr_11 = [HalfByte0, HalfByte0, HalfByte0, [1, 0, 1, 1]];34const BitRepr_17: Adder16<BitRepr_6, BitRepr_11> = [5 [0, 0, 0, 0],6 [0, 0, 0, 0],7 [0, 0, 0, 1],8 [0, 0, 0, 1],9];
Or, simply
1const Value2024: Add<2000, 24> = 2024; // OK2const Value4721: Add<1829, 2892> = 4721; // OK3const Value8001: Add<4000, 4001> = 8001; // OK
If you are interested, please check here
Thank you for reading!