This article is based on Typescript version 4.5 and above and will be published in Denver on 02/07/2022

This article implements a type tool

type result = Add<"9007199254740991", "9007199254740991">

Can compute the sum of two numeric string types, “18014398509481982”

See the code for this article: github.com/kawayiLinLi…

Chinese document: kawayilinlin. Making. IO/typescript -…

As a flamboyant front end, see recently this is not the start of the Winter Olympics, we have to cheer for the athletes, but also think can be a front session of the gymnast, and Olympic athletes together feel the charm of sports in winter

Now, let’s do some exercises! (If you don’t want to read that much, please go to the link in the comments to try it online.)

Basic principles of TS type gymnastics

The if and the else

Condition type, condition type is if to the left of the colon and else to the right

type A = 1
type B = 2
type Example = A extends B ? true : false // false
Copy the code

type Example = A extends B ? True: False: true and false can be understood as code to be written in the if and else branches, respectively

The condition in if is A extends B, whether A can be assigned to B

To implement the else if, you need several of these condition types in combination

Pattern matching

type A = [1.2.3]
type ExampleA = A extends [infer First, ...infer Rest] ? First : never / / 1
type B = "123"
type ExampleB = B extends `${infer FirstChar}${infer Rest}` ? FirstChar : never / / '1'
Copy the code

Pattern matching is one of the most useful TS features that we will use to implement string extensions and tuples based on it

If you want to know more, check out this article: Pattern Matching – Routines that will Make your TS Gymnastic skills explode

Infer: Inferring Within Conditional Types official documentation about Infer in Conditional Types: Inferring Within Conditional Types

With or not

The and or not can be easily implemented based on conditional types

C) Condition D) Condition
// common
// true with, i.e. C1, C2
type And<C1 extends boolean, C2 extends boolean> = C1 extends true
  ? C2 extends true
    ? true
    : false
  : false

// common
// and, i.e. C1, C2 either is true
type Or<C1 extends boolean, C2 extends boolean> = C1 extends true
  ? true
  : C2 extends true
  ? true
  : false

// common
// reverse the true or false state of C
type Not<C extends boolean> = C extends true ? false : true
Copy the code

Ts does not currently support dynamic number of generic parameters, so if there are more than one condition, we need to define more than one different, for example

// common
// There are three conditions
type And3<C1 extends boolean, C2 extends boolean, C3 extends boolean> = And<
  And<C1, C2>,
  C3
>

// common
// There are four conditions
type And4<
  C1 extends boolean,
  C2 extends boolean,
  C3 extends boolean,
  C4 extends boolean
> = And<And3<C1, C2, C3>, C4>
Copy the code

Now, we have encapsulated several type tools And Or Not to achieve the goal of implementing adder based on TS type system

We need many of these types of tools

In order to facilitate management, we need to divide it into modules, such as the above and or not, which are divided into common

We also need function, array, number, Object, and String to handle function types, tuples, numbers, objects, and characters

Judgment is equal

Among the JS operators, there are == and ===

Similar judgments can be made in ts type systems

// common
// Determine whether the left type can be assigned to the right type
type CheckLeftIsExtendsRight<T extends any, R extends any> = T extends R
  ? true
  : false

// common
// Check whether the left type is the same as the right type
type IsEqual<A, B> = (<T>() = > T extends A ? 1 : 2) extends <
  T1
>() = > T1 extends B ? 1 : 2
  ? true
  : false
Copy the code

CheckLeftIsExtendsRight checks whether the Left type can be assigned to the Right type. Unlike ==, which does a cast comparison, the condition Left extends Right? XXX: XXX will only perform structural compatibility checks

Such as

type Example1 = { a: 1; b: 2 } extends { a: 1}?true : false // true
type Example2 = 1 | 2 extends 1 ? true : false // true
Copy the code

Although the two types are different in length, they can be checked by constraint

IsEqual Reference Github – typescript Issue: [Feature Request] Type level equal operator

toString

It is too difficult to infer ts because we cannot infer numbers. How can we infer ts from integer and floating-point? Is it positive or negative? Is there just one numeric type and nothing can be done?

This all requires pattern matching based on character type (or tuple type)

// string
// Converting a type to a string is limited, and only the following types are supported
type CanStringified = string | number | bigint | boolean | null | undefined

// string
// Convert supported types to strings
type Stringify<T extends CanStringified> = `${T}`
Copy the code

The effect

type Example1 = Stringify<0> / / "0"

type Example2 = Stringify<-1> // "-1"

type Example3 = Stringify<0.1> / / "0.1"

type Example4 = Stringify<"0.2"> / / "0.2"
Copy the code

cycle

In JS, we can use for, while, do… Loops like while iterate over iterables, all of which are dependent on one thing, the loop condition

For example, in a for loop, for (initialization; Conditions; Post-loop logic) we usually use a variable I that increments after each loop, and the loop condition is usually to compare I with another number

So we also need to implement a number type size comparison, number type summation tool type

Loops in TS can be implemented recursively, and the concept of type assignment does not exist in the ts type system

type Example = 1
Example = 2 // There is no such way
Copy the code

Only by each recursion, the current generic parameter is treated as the next recursion generic parameter, and when the recursion terminates, the type of one of the current generic parameters is returned

Take a look at this process with the simplest recursive type example

type Example<
  C extends boolean = true,
  Tuple extends unknown[] = [1]
> = C extends true ? Example<false, [...Tuple, 1]> : Tuple

type Result = Example / / [1, 1)

// Two generic arguments to Example
Copy the code

In the example above, Result yields type [1, 1]

Example

Example
,>

The second time: C passes false, it will go to Tuple, Tuple is the value of the last passed value [1, 1], the final return type is [1, 1].

In addition to recursion, there are two other ways to loop, a distributed conditional type and a mapped type, but they are difficult to pass types

// Distributed condition type. When the generic parameter T is a union type, the condition type is distributed and each item in T is distributed to extends separately for comparison
type Example1<T> = T extends number ? T : never

type Result1 = Example1<"1" | "2" | 3 | 4> / / 3 | 4

// Mapping type, fixed, the in operator will distribute T as the key of the new object type
type Example2<T> = {
  [Key in T]: Key
}
type Result2 = Example2<"1" | "2" | 3 | 4> / / {1: "1"; 2: "2"; 3:3; 4:4; }
Copy the code

Basic math

Determine the positive and negative

In some scenarios, we can work with numbers of type number as well as numbers of type string, which we define as NumberLike

type NumberLike = number | `The ${number}`
// Examples of types that can be assigned to NumberLike: number, '${number}', 1, -1, 0.1, -0.1, "1", "-1", etc
Copy the code

Judgment of 0

// N = Number
// number
/ / whether the number/type of 0, whether N can be assigned to 0 | "0"
type IsZero<N extends NumberLike> = common.CheckLeftIsExtendsRight<N, 0 | "0">

// number
// If number is greater than 0, the generic type has a limit on NumberLike, so it must be a number or a string of numbers. After converting it to a string, determine whether the first part of the string is - or greater than zero
type IsOverZero<N extends NumberLike> = IsZero<N> extends true
  ? false
  : common.CheckLeftIsExtendsRight<
      string.Stringify<N> extends `The ${"-"}${infer Rest}` ? Rest : never.never
    >

// number
// If the number type is less than 0, the result of IsOverZero is reversed
type IsLessZero<N extends NumberLike> = common.Not<IsOverZero<N>>
Copy the code

The two together

In the loop section above, we saw that complex utility types can be created by passing modified generic parameters recursively

In this scenario, we can generate dynamic types. The most common types are tuple types, template string types, and union types

The length of the tuple type is accessible: [0, 1, 2][‘length’] the result is 3, and the tuple type can be concatenated: […[0, 1, 2]…[0]][‘length’] is 4

So we can dynamically generate two tuple types of specified length, then concatenate them together, get the length of the concatenated tuple, and get the addition of positive integers (and zeros)

Reference: juejin. Cn/post / 705089…

// array
// Construct a tuple of a certain Length
type GetTuple<Length extends number = 0> = GetTupleHelper<Length>

type GetTupleHelper<
  Length extends number = 0,
  R extends unknown[] = []
> = R["length"] extends Length ? R : GetTupleHelper<Length, [...R, unknown]>
Copy the code
type IntAddSingleHepler<N1 extends number, N2 extends number> = [
  ...array.GetTuple<N1>,
  ...array.GetTuple<N2>
]["length"]

// number
// Positive integer (and 0) addition, T1, T2 Max 999
type IntAddSingle<N1 extends number, N2 extends number> = IntAddSingleHepler<
  N1,
  N2
> extends number
  ? IntAddSingleHepler<N1, N2>
  : number
Copy the code

Compare the size

If you want to implement tuple sort, you must be able to compare numbers

How do you compare the size of numeric types?

Again, it’s based on tuples

Based on two number N1, N2, create different tuples T1, T2, in turn, reduce the length of the two tuples (delete the first or last), when a yuan length is zero, this is the tuple corresponding numeric type, smaller than the other numeric types (or equivalent, so also need to determine whether does not equal to compare)

An implementation to remove the last bit of an array: juejin.cn/post/704553…

Based on pattern matching, the last item and the remaining items are matched and the remaining items are returned

There is no concept of changing the original type in the type system, so the addition, deletion, modification, or query of a tuple type should return the changed type directly, not the changed value

In js, [1, 2, 3].shift() will return 1, [1, 2, 3].pop() will return 3, but in TS type systems, this return is not meaningful, pop <[1, 2, 3]> should return the type [1, 2].

// array
// Remove the last bit of the array
type Pop<T extends unknown[]> = T extends [...infer LeftRest, infer Last]
  ? LeftRest
  : never
Copy the code
// T means Tuple
type CompareHelper<
  N1 extends number,
  N2 extends number,
  T1 extends unknown[] = array.GetTuple<N1>,
  T2 extends unknown[] = array.GetTuple<N2>
> = IsNotEqual<N1, N2, true> extends true
  ? common.Or<IsZero<T1["length"]>, IsZero<T2["length"] > >extends true
    ? IsZero<T1["length"] >extends true
      ? false
      : true
    : CompareHelper<array.Pop<T1>["length"], array.Pop<T2>["length"] > :false

// number
// Compare the size of two numeric types
type Compare<N1 extends number, N2 extends number> = CompareHelper<N1, N2>
Copy the code

Two Numbers subtraction

The logic of subtracting two numeric types is similar to the logic of comparing sizes between two numeric types, except that when the type is returned, the length of the remaining tuple is returned

This implementation is limited by the length of the tuple type and can only get a positive number (or 0), the absolute value of the result

When used with other tool types, the Type instantiation is excessively deep and possibly infinite; please refer to github issue

That is: there is currently a limit of 50 nested instances, which can be circumvent by batch processing (20210714)

// Batch processing example
type GetLetters<Text> =
  Text extends `${infer C0}${infer C1}${infer C2}${infer C3}${infer C4}${infer C5}${infer C6}${infer C7}${infer C8}${infer C9}${infer Rest}`
    ? C0 | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | GetLetters<Rest>
    : Text extends `${infer C}${infer Rest}`
    ? C | GetLetters<Rest>
    : never
Copy the code

Subtraction implementation

type IntMinusSingleAbsHelper<
  N1 extends number,
  N2 extends number,
  T1 extends unknown[] = array.GetTuple<N1>,
  T2 extends unknown[] = array.GetTuple<N2>
> = IsNotEqual<N1, N2, true> extends true
  ? common.Or<IsZero<T1["length"]>, IsZero<T2["length"] > >extends true
    ? IsZero<T1["length"] >extends true
      ? T2["length"]
      : T1["length"]
    : IntMinusSingleAbsHelper<array.Pop<T1>["length"], array.Pop<T2>["length"] > :0

// number
// The absolute value is obtained by subtracting two numeric types
type IntMinusSingleAbs<
  N1 extends number,
  N2 extends number
> = IntMinusSingleAbsHelper<N1, N2>
Copy the code

Although there is a limit to the depth of nesting, written subtraction can not be used, but addition is very useful, we can write a lot of logic with addition

Refer to the JS package tool type

Tool types may depend on each other. If you have not seen one before, go to the corresponding section

Encapsulate the String tool type

Stringify – Stringify the type

/** * converts the supported type to a string *@example
 * type Result = Stringify<0> // "0"
 */
type Stringify<T extends CanStringified> = `${T}`
Copy the code

Principle: TS built-in template string type

GetChars – Gets characters

/ * * *@exports* Gets the character * in the template string type@see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html
 * @example
 * type Result = GetChars<'abc'> // 'a' | 'b' | 'c'
 */
type GetChars<S> = GetCharsHelper<S, never>

/** * optimize GetChars tail-recursive without exporting to tool type */
type GetCharsHelper<S, Acc> = S extends `${infer Char}${infer Rest}`
  ? GetCharsHelper<Rest, Char | Acc>
  : Acc
Copy the code

Principle: Through pattern matching of template string type, use GetCharsHelper to match the first character and the remaining characters of string type, and then put the remaining characters into GetCharsHelper for processing

The result of each match is passed through the Acc parameter. When S is an empty string, S cannot be assigned to ${infer Char}${infer Rest} and goes to the false branch, ending the recursion, that is, returning Acc type

Split-split string

type SplitHelper<
  S extends string,
  SplitStr extends string = "",
  T extends string[] = []
> = S extends `${infer Char}${SplitStr}${infer Rest}`
  ? SplitHelper<Rest, SplitStr, array.Push<T, Char>>
  : S extends string
  ? S extends ""
    ? T
    : array.Push<T, S>
  : never

/** * splits the string into a tuple *@example* type Result = Split (' 1, 2, 3 ', ', '> / / * / [1, 2, 3]
type Split<S extends string, SplitStr extends string = ""> = SplitHelper<
  S,
  SplitStr
>
Copy the code

Principle: Split string type, is the string type into a tuple type, the parameter needs to set a tuple type to return the result

${infer Char}${infer Rest} is ‘1’,’}${infer Rest} is ‘2,3’, and Char is ‘1’ and Rest is ‘2,3’. ${infer Char}${infer Rest} ${infer Char}${infer Rest}

In this case, we simply place the Char matched each time to the last item in the tuple type parameter T, and return the type of T at the end of the match

Note: Array.push is shown below

GetStringLength – Gets the length of the string

/** * gets the length of the string *@example
 * type Result = GetStringLength<"123"> // 3
 */
type GetStringLength<S extends string> = Split<S>["length"]
Copy the code

Principle: The length of a tuple can be obtained. Through the above Split, the string type can be divided into tuple types according to “”, and then the length of the tuple is the length of the string type

CharAt – Gets the character of the string under index bit I

/** * gets the character * under index bit I of the string@example
 * type Result = CharAt<"123", 1> // "2"
 */
type CharAt<S extends string, I extends number> = Split<S>[I]
Copy the code

Principle: Tuple type can be indexed access, you can split string type into tuple type according to “”, and then through the index access, get the character at the index bit I

Concat – Concates two strings

/** * concatenates two strings *@example
 * type Result = Concat<"123", "456"> // "123456"
 */
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`
Copy the code

Principle: TS template string type usage

Includes – Determines whether the string contains substrings

/** * Determines whether the string contains a substring *@example
 * type Result = Includes<"123", "12"> // true
 */
type Includes<
  S1 extends string,
  S2 extends string
> = S1 extends `${infer Left}${S2}${infer Right}` ? true : false
Copy the code

Principle: Pattern matching determines whether a string type contains a substring

StartsWith – determines whether the string StartsWith a substring

/** * Determines whether the string starts with a substring *@example
 * type Result = StartsWith<"123", "12"> // true
 */
type StartsWith<
  S1 extends string,
  S2 extends string
> = S1 extends `${S2}${infer Right}` ? true : false
Copy the code

Principle: During pattern matching, infer Left is not written on the Left, which means that the Left side only contains empty string and no substring with length, that is, StartsWith

EndsWith – Determines whether the string ends in a substring

/** * Determines whether the string ends in a substring *@example
 * type Result = EndsWith<"123", "23"> // true
 */
type EndsWith<
  S1 extends string,
  S2 extends string
> = S1 extends `${infer Left}${S2}` ? true : false
Copy the code

Principle: During pattern matching, infer Right is not written on the Right side, which means that the Right side contains only empty string and no substring with length, that is, EndsWith

IndexOf – Finds the position of a substring from left to right

type IndexOfHelper<
  S1 extends string,
  S2 extends string,
  Len1 extends number = GetStringLength<S1>,
  Len2 extends number = GetStringLength<S2>
> = common.Or<
  number.Compare<Len1, Len2>,
  number.IsEqual<Len1, Len2>
> extends true
  ? S1 extends `${infer Left}${S2}${infer Right}`
    ? GetStringLength<Left>
    : -1
  : -1

/** * find the position of the substring * from left to right@example
 * type Result = IndexOf<"123", "23"> // 1
 */
type IndexOf<S1 extends string, S2 extends string> = IndexOfHelper<S1, S2>
Copy the code

Infer Left}${infer Left}${infer Left}${infer Right} and infer Right}

We can compare the length of the parent string with the length of the child string. If the child string is longer than the parent string, we don’t need to match and return -1

LastIndexOf – Finds the position of the substring from right to left

type LastIndexOfHelper<
  S1 extends string,
  S2 extends string,
  Index extends number = -1 /** Now matches the maximum value from left to right, if not, the last matched index is the first index from right to left */,
  AddOffset extends number = 0 /** Each time an empty string is matched from left to right and replaced with an empty string, the next sequential increment is required */
> = S1 extends `${infer Left}${S2}${infer Right}`
  ? LastIndexOfHelper<
      Replace<S1, S2, "">,
      S2,
      number.IntAddSingle<GetStringLength<Left>, AddOffset>,
      number.IntAddSingle<AddOffset, GetStringLength<S2>>
    >
  : Index

/** * find the position of the substring from right to left *@example
 * type Result = LastIndexOf<"23123", "23"> // 3
 */
type LastIndexOf<S1 extends string, S2 extends string> = LastIndexOfHelper<
  S1,
  S2
>
Copy the code

How it works: Template string pattern matches from left to right, while LastIndexOf matches from right to left, so matches are still based on left to right, but after each match, replace matched substrings with empty ones

Then add up the length of the deleted sections, and the result is a simulated index matching from right to left

Note: See below for Replace

Replace – Finds and replaces a substring in a string

/** * finds and replaces a substring * in a string@example
 * type Result = Replace<"23123", "23", "xx"> // "xx123"
 */
type Replace<
  S extends string,
  MatchStr extends string,
  ReplaceStr extends string
> = S extends `${infer Left}${MatchStr}${infer Right}`
  ? `${Left}${ReplaceStr}${Right}`
  : S
Copy the code

ReplaceStr replaces MatchStr with ReplaceStr

ReplaceAll – Finds and replaces all substrings in a string

/** * finds and replaces all substrings * in the string@example
 * type Result = Replace<"23123", "23", "xx"> // "xx1xx"
 */
type ReplaceAll<
  S extends string,
  MatchStr extends string,
  ReplaceStr extends string
> = Includes<S, MatchStr> extends true
  ? ReplaceAll<Replace<S, MatchStr, ReplaceStr>, MatchStr, ReplaceStr>
  : S
Copy the code

Principle: Based on Replace, Replace recursively, Replace all MatchStr, the termination condition is whether S contains MatchStr

Repeat – A string that repeats Times

type RepeatHelper<
  S extends string,
  Times extends number,
  OriginStr extends string = S,
  Offset extends number = 1
> = Times extends 0
  ? ""
  : number.IsEqual<Times, Offset> extends true
  ? S
  : `${OriginStr}${RepeatHelper<
      S,
      Times,
      OriginStr,
      number.IntAddSingle<Offset, 1>
    >}`

/** * Repeats the Times string *@example
 * type Result = Repeat<"1", 5> // "11111"
 */
type Repeat<S extends string, Times extends number = 1> = RepeatHelper<S, Times>
Copy the code

Principle: If Times is 0, an empty string is returned

Pass the loop condition Offset in the argument (incrementing each pass by 1, i.e. Number.intaddsingle

). End the recursion when the loop condition Offset is equal to the loop Times
,>

In each recursion, a string S is inserted at the beginning of the string, i.e

${first time S}${second time S}${third time S}${the rest... } ` ` `}}Copy the code

Note: number.IntAddSingle, number.IsEqual see below

PadStart – Padding before the string

type PadHelper<
  S extends string,
  N extends number = 0,
  FillS extends string = "",
  IsStart extends boolean = true,
  Len extends number = GetStringLength<S>,
  Offset extends number = Len
> = number.Compare<N, Len> extends true
  ? number.IsEqual<N, Offset> extends true
    ? S
    : PadHelper<
        `${IsStart extends true ? FillS : ""}${S}${IsStart extends false
          ? FillS
          : ""}`,
        N,
        FillS,
        IsStart,
        Len,
        number.IntAddSingle<Offset, 1>
      >
  : S

/** * When the string does not meet the given length, padding the string to meet the length *@example
 * type Result = PadStart<'0123', 10> // '      0123'
 */
type PadStart<
  S extends string,
  N extends number = 0,
  FillS extends string = ""
> = PadHelper<S, N, FillS>
Copy the code

Principle: Compare the specified length and the length of the current string type is equal, if the length is satisfied, directly return S, each recursion, add the specified character to the left of S, until the length of S meets the specified length, the recursion is terminated

PadEnd – Padding after the string

/** * When the string does not meet the given length, the string is padded to meet the length *@example
 * type Result = PadStart<'0123', 10> // '0123      '
 */
type PadEnd<
  S extends string,
  N extends number = 0,
  FillS extends string = ""
> = PadHelper<S, N, FillS, false>
Copy the code

Principle: Compare the given length and the length of the current string type equal, if the length is satisfied, directly return S, each recursion, add the specified character to the right of S, until the length of S meets the given length, end recursion

TrimLeft – Removes space before string

/** * Removes the space * to the left of the string type@see https://juejin.cn/post/7045536402112512007#heading-5
 * @example
 * type Result = PadStart<'   0123'> // '0123'
 */
type TrimLeft<S extends string> = S extends `The ${|""
  | "\t"
  | "\n"}${infer RightRest}`
  ? TrimLeft<RightRest>
  : S
Copy the code

Each time ${one space}${remaining character} is matched and the remaining character continues to match until the rule ${one space}${remaining character} is not met, the recursion is terminated and S is returned

TrimRight – Removes Spaces after strings

/** * Remove the space * to the right of the string type@example
 * type Result = PadStart<'0123   '> // '0123'
 */
type TrimRight<S extends string> = S extends `${infer LeftRest}The ${|""
  | "\t"
  | "\n"}`
  ? TrimRight<LeftRest>
  : S
Copy the code

Each time ${remaining character}${a space} is matched and the remaining character continues to match until the rule ${remaining character}${a space} is not met, the recursion is terminated and S is returned

Trim – Removes whitespace from strings

/** * removes the space * around the string type@example
 * type Result = PadStart<'   0123   '> // '0123'
 */
type Trim<S extends string> = TrimLeft<TrimRight<S>>
Copy the code

Principle: use TrimRight to remove the Spaces on the right, and then send the former result to TrimLeft to remove the Spaces on the left

ToUpperCase – Uppercase string

/** * The string is uppercase *@example
 * type Result = ToUpperCase<'abc'> // 'ABC'
 */
type ToUpperCase<S extends string> = Uppercase<S>
Copy the code

Principle: TS built-in

ToLowerCase – Lowercase string

/** * Change the string to lowercase *@example
 * type Result = ToUpperCase<'ABC'> // 'abc'
 */
type ToLowerCase<S extends string> = Lowercase<S>
Copy the code

Principle: TS built-in

SubString – Intercepts strings between start (inclusive) and end (exclusive)

type SubStringHelper<
  S extends string,
  Start extends number,
  End extends number,
  Offset extends number = 0,
  Cache extends string[] = []
> = number.IsEqual<Offset, End> extends true
  ? array.Join<Cache, "">
  : SubStringHelper<
      S,
      Start,
      End,
      number.IntAddSingle<Offset, 1>,
      common.And3<
        common.Or<number.Compare<Offset, Start>, number.IsEqual<Offset, Start>>,
        common.Or<number.Compare<End, Offset>, number.IsEqual<Offset, End>>,
        CharAt<S, Offset> extends string ? true : false
      > extends true
        ? array.Push<Cache, CharAt<S, Offset>>
        : Cache
    >
/** * intercepts the string * between start (inclusive) and end (exclusive)@example
 * type Result = SubString<'123', 0, 1> // '1'
 */
type SubString<
  S extends string,
  Start extends number,
  End extends number
> = SubStringHelper<S, Start, End>
Copy the code

If the current index is greater than or equal to Start and less than or equal to End, push the current character into a tuple. Array. Join is used to convert the tuple to a string

Note: Array. Join see below

SubStr – Extracts characters from the beginning subscript to the end subscript in the string

/** * extracts a specified number of characters * from the start subscript in the string@example
 * type Result = SubStr<'123', 1, 2> // '23'
 */
type SubStr<
  S extends string,
  Start extends number,
  Len extends number
> = SubStringHelper<S, Start, number.IntAddSingle<Start, Len>>
Copy the code

How it works: SubString needs a Start and an End. If you have Start and Len, you can calculate End first

Encapsulate the array tool type

GetTuple – Constructs a tuple of specified length

/** * construct a tuple of Length *@example
 * type Result = GetTuple<3> // [unknown, unknown, unknown]
 */
type GetTuple<Length extends number = 0> = GetTupleHelper<Length>

type GetTupleHelper<
  Length extends number = 0,
  R extends unknown[] = []
> = R["length"] extends Length ? R : GetTupleHelper<Length, [...R, unknown]>
Copy the code

ArraySet – Changes the type of the specified index bit in a tuple

type SetHelper<
  T extends unknown[],
  Index extends number,
  Value,
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Offset extends T["length"]? Cache : SetHelper< T, Index, Value,number.IntAddSingle<Offset, 1>,
      Push<Cache, Offset extends Index ? Value : T[Offset]>
    >

/** * Changes the type of the index bit specified in the tuple *@example
 * type Result = ArraySet<[1, 2, 3], 2, 4> // [1, 2, 4]
 */
type ArraySet<T extends unknown[], Index extends number, Value> = SetHelper<
  T,
  Index,
  Value
>
Copy the code

Principle: Iterates over tuple types. If Offset is equal to the given index, the corresponding type of the index is replaced by the given type, otherwise, the original type is used

TupleToUnion – Constructs the union type from the element (number) group type

/** * Constructs the union type * from the element (number) group type@example
 * type Result = TupleToUnion<[1, 2, 3]> // 1 | 2 | 3
 */
type TupleToUnion<T extends unknown[]> = T[number]
Copy the code

Principle: Index access of tuple (array) type results in union type

Pop – Removes the last bit of the tuple type

/** * removes the last bit of the tuple *@see https://juejin.cn/post/7045536402112512007#heading-2
 * @example
 * type Result = Pop<[1, 2, 3]> // [1, 2]
 */
type Pop<T extends unknown[]> = T extends [...infer LeftRest, infer Last]
  ? LeftRest
  : never
Copy the code

Principle: Based on pattern matching of tuples, extract the last item and return the remaining items

Shift – Removes the first digit of a tuple type

/** * removes the first * of the array@example
 * type Result = Shift<[1, 2, 3]> // [2, 3]
 */
type Shift<T extends unknown[]> = T extends [infer First, ...infer RightRest]
  ? RightRest
  : never
Copy the code

How it works: Same as Pop

UnShift – Inserts one bit before the tuple

/** * inserts a * before the tuple@example
 * type Result = UnShift<[1, 2, 3], 0> // [0, 1, 2, 3]
 */
type UnShift<T extends unknown[], Item> = [Item, ...T]
Copy the code

A new tuple type can be constructed from a direct write type in [], which writes… Tuple, same as extension operator in JS

Push – Inserts one bit after the tuple

/** * inserts a * at the end of the tuple@example* type Result = Push < 4 > [1, 2, 3], / / [1, 2, 3, 4] * /
type Push<T extends unknown[], Item> = [...T, Item]
Copy the code

Principle: Same as UnShift

Concat – Merges two tuple types

/** * Merge two tuple types *@example
 * type Result = Concat<[1, 2, 3], [4]> // [1, 2, 3, 4]
 */
type Concat<T extends unknown[], R extends unknown[]> = [...T, ...R]
Copy the code

Principle: See UnShift

Join – Concatenates tuple types into string types

/** * concatenates tuple types into string types *@exampleType Result = Join<[1, 2,3]> // "1,2,3" */
type Join<
  T extends string.CanStringified[],
  SplitStr extends string.CanStringified = ""
> = T["length"] extends 0
  ? ""
  : T extends [infer Left, ...infer RightRest]
  ? Left extends string.CanStringified
    ? RightRest extends string.CanStringified[]
      ? `${Left}${T["length"] extends 1 ? "" : SplitStr}${Join< RightRest, SplitStr >}`
      : never
    : never
  : never
Copy the code

${first position}${second position}${third position}

The second position is the substring converted to a string for separation, or an empty string if the length of the tuple is 0

The third position is the rest of the logic, which repeats the original logic

Every – Verifies whether each type in the tuple meets the conditions

type EveryHelper<
  T extends unknown[],
  Check,
  Offset extends number = 0,
  CacheBool extends boolean = true
> = T["length"] extends Offset
  ? CacheBool
  : EveryHelper<
      T,
      Check,
      number.IntAddSingle<Offset, 1>,
      common.And<common.CheckLeftIsExtendsRight<T[Offset], Check>, CacheBool>
    >

/** * Verifies that each type in the tuple meets the condition *@example
 * type Result = Every<[1, 2, 3], number> // true
 */
type Every<T extends unknown[], Check> = T["length"] extends 0
  ? false
  : EveryHelper<T, Check>
Copy the code

Principle: The initial type CacheBool is true. Operations are performed on each type in a tuple in turn with its initial type. If the length is 0, false is returned

Note: common. And, common. CheckLeftIsExtendsRight see below

Some – Verifies whether any type in the tuple matches the conditions

type SomeHelper<
  T extends unknown[],
  Check,
  Offset extends number = 0,
  CacheBool extends boolean = false
> = T["length"] extends Offset
  ? CacheBool
  : SomeHelper<
      T,
      Check,
      number.IntAddSingle<Offset, 1>,
      common.Or<common.CheckLeftIsExtendsRight<T[Offset], Check>, CacheBool>
    >

/** * Verifies whether any type in the tuple matches the condition *@example
 * type Result = Every<['1', '2', 3], number> // true
 */
type Some<T extends unknown[], Check> = SomeHelper<T, Check>
Copy the code

Principle: The initial type CacheBool is false. Operations are performed on or on each type in a tuple in turn with the initial type. False is returned if the length is 0

Note: common. Or, common. CheckLeftIsExtendsRight see below

Fill – Fills the tuple type with the specified type

type FillHelper<
  T extends unknown[],
  F,
  Offset extends number = 0
> = T["length"] extends 0
  ? F[]
  : Offset extends T["length"]? common.IsEqual<T, F[]>extends true /** any[] -> T[] */
    ? T
    : F[]
  : FillHelper<array.Push<array.Shift<T>, F>, F, number.IntAddSingle<Offset, 1>>


/** * populates the tuple type * with the specified type@example
 * type Result = Fill<['1', '2', 3, any], 1> // [1, 1, 1, 1]
 */
type Fill<T extends unknown[], F = undefined> = FillHelper<T, F>
Copy the code

Principle: if the length of the primitive is 0, the tuple F of the new type is returned directly []

If the array type is any[], never[], number[], it should also be replaced by T[].

Otherwise, the recursion is terminated each time the first tuple is removed and a new type is added at the top until the loop condition matches the length of T

Note: commom.IsEqual see below

Filter – Filters out the types of tuples that match the conditions

type FilterHelper<
  T extends unknown[],
  C,
  Strict extends boolean,
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Offset extends T["length"]? Cache : FilterHelper< T, C, Strict,number.IntAddSingle<Offset, 1>,
      common.And<Strict, common.IsEqual<T[Offset], C>> extends true
        ? array.Push<Cache, T[Offset]>
        : common.And<
            common.Not<Strict>,
            common.CheckLeftIsExtendsRight<T[Offset], C>
          > extends true
        ? array.Push<Cache, T[Offset]>
        : Cache
    >

/** * Filter out the type * that matches the tuple type@example
 * type Result = Filter<['1', '2', 3, any, 1], 1, true> // [1]
 */
type Filter<
  T extends unknown[],
  C,
  Strict extends boolean = false
> = FilterHelper<T, C, Strict>
Copy the code

Principle: Strict mode, that is, the value of any cannot be 1 or unknown

. If it is strict mode with common IsEqual to constraint checking, otherwise use common. CheckLeftIsExtendsRight constraint checking

Each time you recurse, if the above conditions are met, it is put into a new tuple type

If the loop condition Offset is equal to the length of T is, the loop is terminated and the new tuple type Cache is returned

Note: common.not see below

MapWidthIndex – Maps a tuple type to an indexed tuple type

interface IndexMappedItem<Item, Index extends number, Tuple extends unknown[]> {
  item: Item
  index: Index
  tuple: Tuple
}

type MapWidthIndexHelper<
  T extends unknown[],
  Offset extends number = 0,
  Cache extends unknown[] = []
> = T["length"] extends Offset
  ? Cache
  : MapWidthIndexHelper<
      T,
      number.IntAddSingle<Offset, 1>,
      Push<Cache, IndexMappedItem<T[Offset], Offset, T>>
    >

/** * maps tuple type to indexed tuple type *@example
 * type Result = MapWidthIndex<[1, 2]> // [{ item: 1; index: 0;tuple: [1, 2]; }, { item: 2; index: 1;tuple: [1, 2]; }]
 */
type MapWidthIndex<T extends unknown[]> = MapWidthIndexHelper<T>
Copy the code

How it works: Declare an interface for constructing items in a new tuple type, and each recursion adds an IndexMappedItem type to the Cache

Note: the array.prototype. map effect cannot be implemented in JS because the utility type with generic parameters cannot be passed as a type directly, and must be passed as a generic parameter first

Find – Finds the first type of the tuple type that matches the condition

type FindHelper<
  T extends unknown[],
  C,
  Offset extends number = 0
> = Offset extends number.IntAddSingle<T["length"].1>?null
  : common.CheckLeftIsExtendsRight<T[Offset], C> extends true
  ? T[Offset]
  : FindHelper<T, C, number.IntAddSingle<Offset, 1>>
/ * * * /
type Find<T extends unknown[], C> = FindHelper<T, C>
Copy the code

If a match is found, the type is returned; otherwise, the type is null

Reverse – reverses the tuple

type ReverseHelper<
  T extends unknown[],
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Cache["length"] extends T["length"]? Cache : ReverseHelper<T,number.IntAddSingle<Offset, 1>, UnShift<Cache, T[Offset]>>
/ * * * /
type Reverse<T extends unknown[]> = ReverseHelper<T>
Copy the code

Principle: Iterates over old tuple types, each time inserting the current type before the new tuple type Cache

FindLast – Finds the last type of the tuple type that matches the condition

/ * * * /
type FindLast<T extends unknown[], C> = Find<Reverse<T>, C>
Copy the code

Principle: Reverse the old tuple type and Find it

FindIndex – Finds the index of the first eligible type of a tuple type

type FindIndexHelper<
  T extends unknown[],
  C,
  Strict extends boolean = false,
  Offset extends number = 0
> = Offset extends number.IntAddSingle<T["length"].1>? -1
  : common.And<common.IsEqual<T[Offset], C>, Strict> extends true
  ? Offset
  : common.And<
      common.CheckLeftIsExtendsRight<T[Offset], C>,
      common.Not<Strict>
    > extends true
  ? Offset
  : FindIndexHelper<T, C, Strict, number.IntAddSingle<Offset, 1>>

/ * * * /
type FindIndex<
  T extends unknown[],
  C,
  Strict extends boolean = false
> = FindIndexHelper<T, C, Strict>
Copy the code

Principle: In strict mode, refer to Filter above to traverse tuples. When matching constraint check, current Offset is returned; otherwise, -1 is returned

FindLastIndex – Finds the index of the last eligible type of a tuple type

type FindLastIndexHelper<
  T extends unknown[],
  C,
  Item = Find<Reverse<MapWidthIndex<T>>, IndexMappedItem<C, number, T>>
> = Item extends IndexMappedItem<C, number, T> ? Item["index"] : -1

type FindLastIndex<T extends unknown[], C> = FindLastIndexHelper<T, C>
Copy the code

MapWidthIndex is used to record the index to each type of tuple. Use Find to match the reversed tuple. When matched, return the Item[‘index’] value of the type as the result

Flat-flat tuple

type FlatHelper<
  T extends unknown[],
  Offset extends number = 0,
  Cache extends unknown[] = []
> = Offset extends T["length"]? Cache : FlatHelper< T,number.IntAddSingle<Offset, 1>,
      T[Offset] extends unknown[]
        ? Concat<Cache, T[Offset]>
        : Push<Cache, T[Offset]>
    >

type Flat<T extends unknown[]> = FlatHelper<T>
Copy the code

Principle: Iterate over tuple types. If the current type does not satisfy the constraint unknown[], Push it into a new tuple, otherwise Concat it

Includes – Whether a qualified type exists in the tuple type

type Includes<T extends unknown[], C> = common.CheckLeftIsExtendsRight<
  C,
  TupleToUnion<T>
>
Copy the code

Principle: Converts a tuple to a union type, true if constraint C can be assigned to the union type

Slice – Extracts a type from a specified start to specified end of a tuple type to construct a new tuple type

type SliceHelper<
  T extends unknown[],
  Start extends number,
  End extends number,
  Offset extends number = 0,
  Cache extends unknown[] = []
> = number.IsEqual<Offset, End> extends true
  ? Cache
  : SliceHelper<
      T,
      Start,
      End,
      number.IntAddSingle<Offset, 1>,
      common.And3<
        common.Or<number.Compare<Offset, Start>, number.IsEqual<Offset, Start>>,
        common.Or<number.Compare<End, Offset>, number.IsEqual<Offset, End>>,
        common.Or<
          number.Compare<T["length"], Offset>,
          number.IsEqual<T["length"], End>
        >
      > extends true
        ? array.Push<Cache, T[Offset]>
        : Cache
    >

type Slice<
  T extends unknown[],
  Start extends number,
  End extends number
> = SliceHelper<T, Start, End>
Copy the code

Principle: Similar to string clipping, iterates over old tuples and pushes these types into new tuples when the loop condition Offset is greater than or equal to Start or less than or equal to End

Sort Sort –

type SortHepler2<
  T extends number[],
  Offset extends number = 0,
  Offset1 extends number = 0,
  Offset1Added extends number = number.IntAddSingle<Offset1, 1>,
  Seted1 extends unknown[] = ArraySet<T, Offset1Added, T[Offset1]>,
  Seted2 extends unknown[] = ArraySet<Seted1, Offset1, T[Offset1Added]>
> = number.IntAddSingle<
  number.IntAddSingle<Offset, Offset1>,
  1
> extends T["length"]? SortHepler1<T,number.IntAddSingle<Offset, 1>>
  : SortHepler2<
      number.Compare<T[Offset1], T[Offset1Added]> extends true
        ? Seted2 extends number[]? Seted2 :never
        : T,
      number.IntAddSingle<Offset1, 1>
    >

type SortHepler1<
  T extends number[],
  Offset extends number = 0
> = Offset extends T["length"]? T : SortHepler2<T, Offset>type Sort<T extends number[]> = SortHepler1<T>
Copy the code

Principle: the simplest bubble sort, each sort, will be large and small replacement

Note: Limited by the depth of nested instances, only two types of tuples can be arranged

Encapsulate number Tool type

IsZero – Determines the type to be 0

/** * number Specifies whether the type is 0 *@example
 * type Result = IsZero<0> // true 
 */
type IsZero<N extends NumberLike> = common.CheckLeftIsExtendsRight<N, 0 | "0">
Copy the code

Note: Principle see above, NumberLike see above

IsOverZero – Whether the value is greater than 0

/** * Number Specifies whether the number type is greater than 0 *@example
 * type Result = IsOverZero<2> // true 
 */
type IsOverZero<N extends NumberLike> = IsZero<N> extends true
  ? false
  : common.CheckLeftIsExtendsRight<
      string.Stringify<N> extends `The ${"-"}${infer Rest}` ? Rest : false.false
    >
Copy the code

Note: Principle see above

IsLessZero – Is less than 0

/** * Number Specifies whether the number type is less than 0 *@example
 * type Result = IsLessZero<-2> // true 
 */
type IsLessZero<N extends NumberLike> = common.Not<IsOverZero<N>>
Copy the code

IsFloat – Whether it is floating point

/** * number is a decimal *@example
 * type Result = IsFloat<1.2> // true 
 */
type IsFloat<
  N extends NumberLike,
  OnlyCheckPoint extends boolean = true
> = string.Stringify<N> extends `${infer Left}The ${"."}${infer Right}`
  ? OnlyCheckPoint extends true
    ? true
    : common.Not<array.Every<string.Split<Right>, "0"> > :false
Copy the code

Principle: After converting to string type, determine whether decimal point

IsInt – Whether it is an integer

/** * number Specifies whether the type is an integer *@example
 * type Result = IsInt<1> // true 
 */
type IsInt<
  N extends NumberLike,
  OnlyCheckPoint extends boolean = true
> = common.Not<IsFloat<N, OnlyCheckPoint>>
Copy the code

Principle: reverse IsFloat

IsEqual – whether the number types are equal

/** * Whether the two number types are equal *@example
 * type Result = IsEqual<1, 1> // true 
 */
type IsEqual<
  L extends NumberLike,
  R extends NumberLike,
  Strict extends boolean = true
> = Strict extends true
  ? common.CheckLeftIsExtendsRight<L, R>
  : common.CheckLeftIsExtendsRight<string.Stringify<L>, string.Stringify<R>>
Copy the code

Principle: see above

IsNotEqual – Whether numeric types are not equal

/** * Whether two number types are not equal *@example
 * type Result = IsNotEqual<1, 2> // true 
 */
type IsNotEqual<
  L extends NumberLike,
  R extends NumberLike,
  Strict extends boolean = true
> = common.Not<IsEqual<L, R, Strict>>
Copy the code

Principle: IsEqual is inverse

IntAddSingle – Adds integers

type IntAddSingleHepler<N1 extends number, N2 extends number> = [
  ...array.GetTuple<N1>,
  ...array.GetTuple<N2>
]["length"]

/** * positive integer (and 0) addition, A1, A2 Max 999 *@see https://juejin.cn/post/7050893279818317854#heading-8
 * @example
 * type Result = IntAddSingle<1, 2> // 3 
 */
type IntAddSingle<N1 extends number, N2 extends number> = IntAddSingleHepler<
  N1,
  N2
> extends number
  ? IntAddSingleHepler<N1, N2>
  : number
Copy the code

Principle: see above

Compare – Compares sizes

type CompareHelper<
  N1 extends number,
  N2 extends number,
  A1 extends unknown[] = array.GetTuple<N1>,
  A2 extends unknown[] = array.GetTuple<N2>
> = IsNotEqual<N1, N2, true> extends true
  ? common.Or<IsZero<A1["length"]>, IsZero<A2["length"] > >extends true
    ? IsZero<A1["length"] >extends true
      ? false
      : true
    : CompareHelper<array.Pop<A1>["length"], array.Pop<A2>["length"] > :false

/** * compare the size *@example
 * type Result = Compare<1, 2> // false 
 */
type Compare<N1 extends number, N2 extends number> = CompareHelper<N1, N2>
Copy the code

Principle: see above

IntMinusSingleAbs – Subtract two numbers

type IntMinusSingleAbsHelper<
  N1 extends number,
  N2 extends number,
  A1 extends unknown[] = array.GetTuple<N1>,
  A2 extends unknown[] = array.GetTuple<N2>
> = IsNotEqual<N1, N2, true> extends true
  ? common.Or<IsZero<A1["length"]>, IsZero<A2["length"] > >extends true
    ? IsZero<A1["length"] >extends true
      ? A2["length"]
      : A1["length"]
    : IntMinusSingleAbsHelper<array.Pop<A1>["length"], array.Pop<A2>["length"] > :0

/** **@example
 * type Result = IntMinusSingleAbs<2, 1> // 1 
 */
type IntMinusSingleAbs<
  N1 extends number,
  N2 extends number
> = IntMinusSingleAbsHelper<N1, N2>
Copy the code

Principle: see above

GetHalf – Gets half of the current numeric type

type GetHalfHelper<N extends number, Offset extends number = 0> = IsEqual<
  IntAddSingle<Offset, Offset>,
  N
> extends true
  ? Offset
  : IsEqual<IntAddSingle<IntAddSingle<Offset, Offset>, 1>, N> extends true
  ? IntAddSingle<Offset, 1>
  : GetHalfHelper<N, IntAddSingle<Offset, 1>>

/** * gets half of the current numeric type *@example
 * type Result = GetHalf<4> // 2 
 */
type GetHalf<N extends number> = GetHalfHelper<N>
Copy the code

Principle: cycle, when Offset + Offset is equal to N, or Offset + 1 + Offset is equal to N, Offset is the result

ToNumber – string type ToNumber type

/ * *@see https://juejin.cn/post/6999280101556748295#heading-68 */
type Map = {
  "0": []
  "1": [1]
  "2": [...Map["1"].1]
  "3": [...Map["2"].1]
  "4": [...Map["3"].1]
  "5": [...Map["4"].1]
  "6": [...Map["5"].1]
  "Seven": [...Map["6"].1]
  "8": [...Map["Seven"].1]
  "9": [...Map["8"].1]}type Make10Array<T extends any[]> = [
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T
]

type ToNumberHelper<
  S extends string,
  L extends any[] = []
> = S extends `${infer F}${infer R}`
  ? ToNumberHelper<
      R,
      [...Make10Array<L>, ...(F extends keyof Map ? Map[F] : never)]
    >
  : L["length"]

/** * String type to number type *@example
 * type Result = ToNumber<"100"> // 100
 */
type ToNumber<S extends string> = ToNumberHelper<S>
Copy the code

Principle: To create a mapping table of characters and tuples, we know that the number type can be obtained by the length of tuples through the previous exploration, so we can get the result by constructing tuples of different lengths according to each character

Make10Array is to multiply the previous result by 10

See juejin. Cn/post / 699928…

Add – Adds numbers

See below

Encapsulate the Object tool type

KeysToUnion – All key-to-union types of object types

type KeysToUnion<T> = keyof T
Copy the code

Values – Gets the union type of the Values of the object type

type Values<T> = T[KeysToUnion<T>]
Copy the code

KeysToTuple – Gets the tuple type of the object type key

type KeysToTuple<T> = KeysToUnion<T>[]
Copy the code

ExtractValues – Filter out attributes that match type V

type ExtractValues<T, V> = {
  [Key in keyof T as T[Key] extends V ? Key : never]: T[Key]
}
Copy the code

ExcludeValues – Filters out attributes that do not match type V

type ExcludeValues<T, V> = {
  [Key in keyof T as T[Key] extends V ? never : Key]: T[Key]
}
Copy the code

GetterSetterPrefix – Adds the get and set prefixes to the object type

type GetterSetterPrefix<T> = {
  [Key in keyof T as Key extends string ? `get${Capitalize<Key>}` : never]: {
    (): T[Key]
  }
} & {
  [Key in keyof T as Key extends string ? `set${Capitalize<Key>}` : never]: {
    (val: T[Key]): void
  }
} & T
Copy the code

Proxify – Converts each attribute value of an object type into get and set form

type Proxify<T> = {
  [P in keyof T]: {
    get(): T[P]
    set(v: T[P]): void}}Copy the code

NullableValue – Converts each attribute value of the object type to nullable

type NullableValue<T> = {
  [Key inkeyof T]? : common.Nullable<T[Key]> }Copy the code

Include – Extracts key names that match type U to construct a new object type

type Include<T extends object, U extends keyof any> = {
  [Key in keyof T as Key extends U ? Key : never]: T[Key]
}
Copy the code

ChangeRecordType – Populates the attribute value of the object type with type T

type ChangeRecordType<K, T = undefined> = {
  [P inkeyof K]? : T }Copy the code

Mutable – To be writable

type Mutable<T> = {
  -readonly [P in keyof T]: T[P]
}
Copy the code

ReadonlyPartial – Becomes read-only and optional

type ReadonlyPartial<T> = {
  readonly [P inkeyof T]? : T[P] }Copy the code

DeepPartial – Makes all attributes of an object type optional

type DeepPartial<T> = {
  [Key inkeyof T]? : T[Key]extends object ? DeepPartial<T[Key]> : T[Key]
}
Copy the code

ChainedAccessUnion – Finds all paths to the object type

type ChainedAccessUnion<T extends object> = ChainedAccessUnionHelper<T>

type ChainedAccessUnionHelper<
  T,
  A = {
    [Key in keyof T]: T[Key] extends string ? never : T[Key]
  },
  B = {
    [Key in keyof A]: A[Key] extends never
      ? never
      : A[Key] extends object
      ?
          | `${Extract<Key, string>}.${Extract<keyof A[Key], string>}`
          | (ChainedAccessUnionHelper<A[Key]> extends infer U
              ? `${Extract<Key, string>}.${Extract<U, string>}`
              : never)
      : never
  }
> = T extends object
  ? Exclude<keyof A | Exclude<Values<B>, never>, never>
  : never
Copy the code

Encapsulate the common tool type

The Not –

type Not<C extends boolean> = C extends true ? false : true
Copy the code

And – And

type And<C1 extends boolean, C2 extends boolean> = C1 extends true
  ? C2 extends true
    ? true
    : false
  : false
Copy the code

The Or – Or

type Or<C1 extends boolean, C2 extends boolean> = C1 extends true
  ? true
  : C2 extends true
  ? true
  : false
Copy the code

CheckLeftIsExtendsRight – Constraint verification

type CheckLeftIsExtendsRight<T extends any, R extends any> = T extends R
  ? true
  : false
Copy the code

IsEqual – the type is strictly equal

/** * https://github.com/microsoft/TypeScript/issues/27024#issuecomment-510924206 */
type IsEqual<A, B> = (<T>() = > T extends A ? 1 : 2) extends <
  T1
>() = > T1 extends B ? 1 : 2
  ? true
  : false
Copy the code

IsAny – whether the type IsAny

type IsAny<T> = 0 extends (1 & T) ? true : false
Copy the code

Differences between the Diff –

type Diff<T, C> = Exclude<T, C> | Exclude<C, T>
Copy the code

SumAggregate – and set

type SumAggregate<T, U> = T | U
Copy the code

Nullable – Can be null

type Nullable<T> = T | null | undefined
Copy the code

Encapsulate the function utility type

Noop – Common function type

type Noop = (. args:any) = > any
Copy the code

GetAsyncFunctionReturnType – for asynchronous function return value

type GetAsyncFunctionReturnType<F extends Noop> = Awaited<ReturnType<F>>
Copy the code

GetFunctionLength – Gets the parameter length

type GetFunctionLength<F extends Noop> = F extends(... args: infer P) =>any
  ? P["length"]
  : never
Copy the code

Implement an adder!

Analysis of the

Let’s recall that in third grade math, how do you add decimal numbers in base 10?

Such as 1.8 + 1.52

Do I have to line up the decimals first

  1.8
+ 1.52
——————
  2.32
Copy the code

Then compute from right to left. If the current bit is greater than or equal to 10, you need to carry one bit to the left

So how does TS know that 1 plus 1 is 2, 1 plus 2 is 3?

We can define a mapping table, a two-dimensional tuple, to obtain the result of a bit integer addition and its carry condition by means of index access

implementation

What is a string type like a number

${number} ${number} ${number}

But a number like this would also qualify: “000.1”

If you want to limit such numbers, which are well handled with regular expressions, how do you limit them in the TS type system?

We can define the type of case except when we have more than one zero in front of the decimal point

/ / each number
type Numbers = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

// Cannot start with multiple zeros
type AdvancedNumericCharacters =
  | `The ${0}.The ${number}`
  | `${Exclude<Numbers, 0>}The ${number | ""}.The ${number}`
  | `${Exclude<Numbers, 0>}${Numbers | ""}.The ${number}`
  | `${Exclude<Numbers, 0>}The ${number}`
  | `${Numbers}`
Copy the code

Defining an addition table

If we have 1 + 1, and the addition table is AddMap, we want to use AddMap[1][1] like this to get the result of the addition and the result of the rounding

That is:

type AddMap = [
    [/* 0 + 0 */ { result: 0.add: 0 }, /* 0 + 1 */ {/ *... * /} / *... * /]
    // ...
]
Copy the code

The data processing

Elementary school third grade math addition, from right to left, to align with the decimal point

But in the string or tuple processing tool we implemented above, from right to left is very troublesome, so it is easier to go from left to right, and tuple operation is more convenient than string operation, so in the actual addition operation

The actual data we are dealing with should be a reversed tuple

  1. Align by decimal point

That is, divide by the decimal point, and fill in the zeros with PadStart and PadEnd

/ / type Result = SplitByPoint < "1.02" > / / / "1", "02"
// If there is no decimal point, the decimal place complements 0
type SplitByPoint<S extends AdvancedNumericCharacters> = string.Includes<
  S,
  "."
> extends true
  ? string.Split<S, ".">
  : [S, "0"]

/ / type Result = AddHelperSplitToArr < "1.02", "0.123" > / / [[" 1 ", "02"], [" 10 ", "123"]]
// We need to split two numbers together
type AddHelperSplitToArr<
  S1 extends AdvancedNumericCharacters,
  S2 extends AdvancedNumericCharacters,
  Result = [SplitByPoint<S1>, SplitByPoint<S2>]
> = Result extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]]? Result :never
Copy the code
// type Result = AddFillZeroHelper<[["1", "02"], ["10", "123"]]> // [["01", "020"], ["10", "123"]]
// Add 0 to the result with PadStart and PadEnd
type AddFillZeroHelper<
  Data extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]],
  Result = [
    [
      string.PadStart<Data[0] [0].string.GetStringLength<Data[1] [0] >,"0">,
      string.PadEnd<Data[0] [1].string.GetStringLength<Data[1] [1] >,"0">], [string.PadStart<Data[1] [0].string.GetStringLength<Data[0] [0] >,"0">,
      string.PadEnd<Data[1] [1].string.GetStringLength<Data[0] [1] >,"0">
    ]
  ]
> = Result extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]]? Result :never
Copy the code
  1. Backward reversal of tuples facilitates calculation from left to right
// type Result = AddFillZeroHelper<[["1", "02"], ["10", "123"]]> 
/ / [[[" 1 ", "0"], [" 0 ", "2", "0"]], [[" 0 ", "1"], [" 3 ", "2", "1"]]]
type AddReverseData<
  Data extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]],
  Result = [
    [
      array.Reverse<string.Split<Data[0] [0]>>,
      array.Reverse<string.Split<Data[0] [1]>>
    ],
    [
      array.Reverse<string.Split<Data[1] [0]>>,
      array.Reverse<string.Split<Data[1] [1]>>
    ]
  ]
> = Result extends[[`${Numbers}`[].`${Numbers}`[]],
  [`${Numbers}`[].`${Numbers}`[]]]? Result :never
Copy the code

Start counting

Computes decimal or integer bits separately to reduce complexity. If there is a carry, add “10” to the front of the tuple

type StepAdderHelper<
  DataLeft extends `${Numbers}`[].// The integer part
  DataRight extends `${Numbers}`[].// The decimal part
  Curry extends `${Numbers}` = `The ${0}`.// Whether there is a carry currently
  Offset extends number = 0.// The offset of the loop
  ResultCache extends `The ${number}`[] = [], // Used to cache results
  NextOffset extends number = number.IntAddSingle<Offset, 1>, // The offset is increased by 1
  Current extends AddMap[Numbers][Numbers] = AddMap[DataLeft[Offset]][DataRight[Offset]], // The current result
  CurrentWidthPreCurry extends `${Numbers}` = AddMap[Current["result"]][Curry]["result"] // The current actual result (plus carry)
> = DataLeft["length"] extends DataRight["length"]?`${Offset}` extends `${DataLeft["length"]}`
    ? ResultCache
    : StepAdderHelper<
        DataLeft,
        DataRight,
        Current["add"],
        NextOffset,
        common.And<
          number.IsEqual<Current["add"]."1">,
          number.IsEqual<`${NextOffset}`.`${DataLeft["length"]}`>
        > extends true
          ? array.Push<["10". ResultCache], CurrentWidthPreCurry> : array.Push<ResultCache, CurrentWidthPreCurry> > :never
Copy the code

Joining together the results

type NumbersWidthCurry = Numbers | 10

type MergeResultHelper<
  Data extends[[`${Numbers}`[].`${Numbers}`[]],
    [`${Numbers}`[].`${Numbers}`[]]].// The processed data
  LeftInt extends `${Numbers}`[] = Data[0] [0].// The integer part of the addend
  LeftFloat extends `${Numbers}`[] = Data[0] [1].// The decimal part of the addend
  RightInt extends `${Numbers}`[] = Data[1] [0].// The integer part of the addend
  RightFloat extends `${Numbers}`[] = Data[1] [1].// The decimal part of the addend
  FloatAdded extends `${NumbersWidthCurry}`[] = StepAdderHelper<
    LeftFloat,
    RightFloat
  >, // Decimal part addition, with the result of an antiordered tuple of carry
  FloatHasCurry extends boolean = FloatAdded[0] extends "10" ? true : false.// Whether the decimal has a carry
  DeleteCurryFloatResult extends unknown[] = FloatHasCurry extends true
    ? array.Shift<FloatAdded>
    : FloatAdded, // Unordered tuple that can be used directly after the decimal part is removed
  IntAdded extends `${NumbersWidthCurry}`[] = StepAdderHelper<
    LeftInt,
    RightInt,
    FloatHasCurry extends true ? 1 ` ` : "0"
  >, // Integer part addition, the initial will be accompanied by the decimal part of the carry, the result will carry its own carry
  IntHasCurry extends boolean = IntAdded[0] extends "10" ? true : false.// Whether the integer part is carried
  DeleteCurryIntResult extends unknown[] = IntHasCurry extends true
    ? array.Shift<IntAdded>
    : IntAdded, // The integer part deletes the reverse tuple that can be used directly after the carry
  ResultReversed = array.Reverse<
    LeftFloat["length"] extends 0
      ? DeleteCurryIntResult
      : array.Concat<
          [...DeleteCurryFloatResult, "."],
          [...DeleteCurryIntResult]
        >
  >, // Add the integer decimal (decimal point) to the result and restore the unordered tuple
  FloatResult = array.Join<
    ResultReversed extends string[]? IntHasCurryextends true
        ? ["1". ResultReversed] : ResultReversed :never.""
  > // Converts strings and handles the carry of integers
> = FloatResult

// Final result
type Add<
  S1 extends AdvancedNumericCharacters,
  S2 extends AdvancedNumericCharacters
> = MergeResultHelper<
  AddReverseData<AddFillZeroHelper<AddHelperSplitToArr<S1, S2>>>
>
Copy the code

The final code

type Numbers = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

type AdvancedNumericCharacters =
  | `The ${0}.The ${number}`
  | `${Exclude<Numbers, 0>}The ${number | ""}.The ${number}`

type AddMap = [
  [
    { result: "0"; add: "0" }, / / 00
    { result: "1"; add: "0" }, / / 01
    { result: "2"; add: "0" }, / /.
    { result: "3"; add: "0" }, // 03
    { result: "4"; add: "0" }, // 04
    { result: "5"; add: "0" }, / / 05
    { result: "6"; add: "0" }, // 06
    { result: "Seven"; add: "0" }, / / 07
    { result: "8"; add: "0" }, / / 08
    { result: "9"; add: "0" } / / 09], [{result: "1"; add: "0" }, / / 10
    { result: "2"; add: "0" }, / / 11
    { result: "3"; add: "0" }, / / 12
    { result: "4"; add: "0" }, / / 13
    { result: "5"; add: "0" }, / / 14
    { result: "6"; add: "0" }, / / 15
    { result: "Seven"; add: "0" }, / / 16
    { result: "8"; add: "0" }, / / 17
    { result: "9"; add: "0" }, / / 18
    { result: "0"; add: "1" } / / 19]./ /...[{result: "8"; add: "0" }, / / 80
    { result: "9"; add: "0" }, / / 81
    { result: "0"; add: "1" }, / / 82
    { result: "1"; add: "1" }, / / 83
    { result: "2"; add: "1" }, / / 84
    { result: "3"; add: "1" }, / / 85
    { result: "4"; add: "1" }, / / 86
    { result: "5"; add: "1" }, / / 87
    { result: "6"; add: "1" }, / / 88
    { result: "Seven"; add: "1" } / / 89], [{result: "9"; add: "0" }, / / 90
    { result: "0"; add: "0" }, / / 91
    { result: "1"; add: "1" }, / / 92
    { result: "2"; add: "1" }, / / 93
    { result: "3"; add: "1" }, / / 94
    { result: "4"; add: "1" }, / / 95
    { result: "5"; add: "1" }, / / 96
    { result: "6"; add: "1" }, / / 97
    { result: "Seven"; add: "1" }, / / 98
    { result: "8"; add: "1" } / / 99]]type SplitByPoint<S extends AdvancedNumericCharacters> = string.Includes<
  S,
  "."
> extends true
  ? string.Split<S, ".">
  : [S, "0"]

type AddHelperSplitToArr<
  S1 extends AdvancedNumericCharacters,
  S2 extends AdvancedNumericCharacters,
  Result = [SplitByPoint<S1>, SplitByPoint<S2>]
> = Result extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]]? Result :never

type AddFillZeroHelper<
  Data extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]],
  Result = [
    [
      string.PadStart<Data[0] [0].string.GetStringLength<Data[1] [0] >,"0">,
      string.PadEnd<Data[0] [1].string.GetStringLength<Data[1] [1] >,"0">], [string.PadStart<Data[1] [0].string.GetStringLength<Data[0] [0] >,"0">,
      string.PadEnd<Data[1] [1].string.GetStringLength<Data[0] [1] >,"0">
    ]
  ]
> = Result extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]]? Result :never

type AddReverseData<
  Data extends [[`The ${number}`.`The ${number}`], [`The ${number}`.`The ${number}`]],
  Result = [
    [
      array.Reverse<string.Split<Data[0] [0]>>,
      array.Reverse<string.Split<Data[0] [1]>>
    ],
    [
      array.Reverse<string.Split<Data[1] [0]>>,
      array.Reverse<string.Split<Data[1] [1]>>
    ]
  ]
> = Result extends[[`${Numbers}`[].`${Numbers}`[]],
  [`${Numbers}`[].`${Numbers}`[]]]? Result :never

type StepAdderHelper<
  DataLeft extends `${Numbers}`[],
  DataRight extends `${Numbers}`[],
  Curry extends `${Numbers}` = `The ${0}`,
  Offset extends number = 0,
  ResultCache extends `The ${number}`[] = [],
  NextOffset extends number = number.IntAddSingle<Offset, 1>,
  Current extends AddMap[Numbers][Numbers] = AddMap[DataLeft[Offset]][DataRight[Offset]],
  CurrentWidthPreCurry extends `${Numbers}` = AddMap[Current["result"]][Curry]["result"]
> = DataLeft["length"] extends DataRight["length"]?`${Offset}` extends `${DataLeft["length"]}`
    ? ResultCache
    : StepAdderHelper<
        DataLeft,
        DataRight,
        Current["add"],
        NextOffset,
        common.And<
          number.IsEqual<Current["add"]."1">,
          number.IsEqual<`${NextOffset}`.`${DataLeft["length"]}`>
        > extends true
          ? array.Push<["10". ResultCache], CurrentWidthPreCurry> : array.Push<ResultCache, CurrentWidthPreCurry> > :never

type NumbersWidthCurry = Numbers | 10

type MergeResultHelper<
  Data extends[[`${Numbers}`[].`${Numbers}`[]],
    [`${Numbers}`[].`${Numbers}`[]]
  ],
  LeftInt extends `${Numbers}`[] = Data[0] [0],
  LeftFloat extends `${Numbers}`[] = Data[0] [1],
  RightInt extends `${Numbers}`[] = Data[1] [0],
  RightFloat extends `${Numbers}`[] = Data[1] [1],
  FloatAdded extends `${NumbersWidthCurry}`[] = StepAdderHelper<
    LeftFloat,
    RightFloat
  >,
  FloatHasCurry extends boolean = FloatAdded[0] extends "10" ? true : false,
  DeleteCurryFloatResult extends unknown[] = FloatHasCurry extends true
    ? array.Shift<FloatAdded>
    : FloatAdded,
  IntAdded extends `${NumbersWidthCurry}`[] = StepAdderHelper<
    LeftInt,
    RightInt,
    FloatHasCurry extends true ? 1 ` ` : "0"
  >,
  IntHasCurry extends boolean = IntAdded[0] extends "10" ? true : false,
  DeleteCurryIntResult extends unknown[] = IntHasCurry extends true
    ? array.Shift<IntAdded>
    : IntAdded,
  ResultReversed = array.Reverse<
    LeftFloat["length"] extends 0
      ? DeleteCurryIntResult
      : array.Concat<
          [...DeleteCurryFloatResult, "."],
          [...DeleteCurryIntResult]
        >
  >,
  FloatResult = array.Join<
    ResultReversed extends string[]? IntHasCurryextends true
        ? ["1". ResultReversed] : ResultReversed :never.""
  >
> = FloatResult

type Add<
  S1 extends AdvancedNumericCharacters,
  S2 extends AdvancedNumericCharacters
> = MergeResultHelper<
  AddReverseData<AddFillZeroHelper<AddHelperSplitToArr<S1, S2>>>
>

type add = Add<"9007199254740991"."9007199254740991">
Copy the code

Related articles recommended

In this paper, the original

Nuggets: TypeScript TypeScript Compilation of gymnastic postures

Nuggets: Ts Masters: 22 examples that delve into the most esoteric of Ts’s advanced type tools

Nuggets: Let’s do some exercises! Dive into TypeScript advanced types and type gymnastics

Nuggets: Pattern matching – a routine that gives you an explosion of ts gymnastic skills