# A non-recursive type-level inclusion operator

```
type E1<X> = <T>() => T extends X ? 0 : 1
type E2<X> = <T>() => T extends X ? 0 : 1
type IsEqual<X, Y> = E1<X> extends E2<Y> ? true : false
/**
* Whether or not T includes U as an element.
*/
type Includes<T extends readonly unknown[], U> = true extends {
[key in keyof T]: IsEqual<T[key], U>
}[number] ? true : false
```

# A non-recursive type-level `Includes`

operator in Typescript

## Introduction

`Includes`

is a type-level operator that determines whether a given type `T`

includes a given type `U`

as an element. In other words, it returns `true`

if `T`

is a subtype of `U`

.

This operator is implemented using a simple helper function, `IsEqual`

, which compares two types `X`

and `Y`

and returns `true`

if they are equal.

## Implementation

### IsEqual

The `IsEqual`

function is implemented as follows:

```
type IsEqual<X, Y> = E1<X> extends E2<Y> ? true : false
```

where `E1`

and `E2`

are helper functions that take a type `T`

and return `0`

if `T`

is equal to `X`

, and `1`

otherwise.

Thus, `IsEqual<X, Y>`

returns `true`

if `E1<X>`

is equal to `E2<Y>`

, and `false`

otherwise.

#### E1 and E2

The `E1`

and `E2`

functions are implemented as follows:

```
type E1<X> = <T>() => T extends X ? 0 : 1
type E2<X> = <T>() => T extends X ? 0 : 1
```

These functions take a type `T`

and return `0`

if `T`

is equal to `X`

, and `1`

otherwise. These functions exploit deep behavior around generics to implement a “true type-level equality check”, which will even distinguish readonly attributes from non-readonly attributes.

The deep behavior needed to distinguish readonly attributes does not work without *both* type declarations, despite the equivalence. The internal type-checking behavior seems to depend on comparing two *separate* type identifiers.

### Includes

The `Includes`

function is implemented as follows:

```
type Includes<T extends readonly unknown[], U> = true extends {
[key in keyof T]: IsEqual<T[key], U>
}[number] ? true : false
```

First, note that `Includes`

takes two type parameters: `T`

, which is a *readonly* array type, and `U`

, which is the element type that we want to check for inclusion in `T`

. We do not introduce a constraint whereby U must be an element of T, since the whole purpose of the function is to determine whether or not U is an element of T.

Next, we define a helper type, `R`

, which is a *mapped type*. This is a type whose properties are determined by mapping a given type `T`

to another type `U`

. In this case, we are mapping each element of `T`

to the result of `IsEqual<T[key], U>`

.

Thus, `R`

is a type with one property for each element of `T`

, whose value is `true`

if the element is equal to `U`

, and `false`

otherwise.

Finally, we return `true`

if `R`

has a property with value `true`

, and `false`

otherwise.

## Advantages

The `Includes`

operator has a number of advantages over other implementations.

First, it is *non-recursive*. This means that it will not suffer from *exponential typechecking*, which is a problem with other implementations of `Includes`

.

Second, it is *type-safe*. This means that it will only return `true`

if `U`

is *actually* an element of `T`

. Other implementations may return `true`

even if `U`

is not an element of `T`

.

Third, it is *efficient*. This means that it will not introduce *unnecessary* type-checking constraints. Other implementations may introduce such constraints, which can lead to *slow* type-checking.

## Conclusion

The `Includes`

operator is a simple, efficient, and type-safe way to determine whether a given type `T`

includes a given type `U`

as an element.