Typescript Test Your Generic Type Part 2

Acid Coder
3 min readJun 29, 2022

--

read the part 1 here

now we know how to create tests for our type, but it is not done yet, there are edge cases that we need to take care of, and we need to understand some theories before we can solve them.

now take a look again at our Expect type

type Expect<T, U>= T extends U ? U extends T ? true : false : false

notice the T and U?

they are known as naked type parameters.

You can think of them as some kind of variable.

so if there are naked type parameters, then there are also non-naked type parameters and they look like this, examples:

  1. keyof T
  2. U[]
  3. [U]
  4. SomeGeneric< T >

naked parameter distributes over a union, while non-naked and types do not distribute over a union(just like normal types), to understand this, let’s take a look at the code below

normal:

type A = "a" | "b" extends "a" ? true : false // false 
type B = "a" extends "a" | "b" ? true : false // true

pretty straightforward

"a" | "b" cannot be narrowed down from "a", because "a" | "b" is wider, so "a" | "b" could not extend "a" and vice versa

naked parameter:

type C<T,U> = T extends U ? true : false
type r1 = C<"a" | "b", "a"> // boolean
type r2 = C<"a", "a" | "b"> // true

in the case of r1, "a" | "b" try to distribute over "a": ("a" extends "a"? true : false) | ("b" extends "a"? true : false)

"a" extends "a" is true, "b" extends "a" is false, in the end, we get true | false which is equivalent to boolean

in the case of r2, "a" is not a union, so there is no distribution, and since "a" can be narrowed down from "a" | "b", hence you get true

playground

By this point, we know what could go wrong with Expect, this time let us use Odd Number Type as our test subject

type OddNumber<
X extends number,
Y extends unknown[] = [1],
Z extends number = never
> = Y['length'] extends X
? Z | Y['length']
: OddNumber<X, [1, 1, ...Y], Z | Y['length']>
type Expect<T, U> = T extends U ? (U extends T ? true : false) : false// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const assert = <T extends true>() => {
//
}
assert<Expect<OddNumber<5>,1 | 3 | 5>>() // true, pass test
// @ts-expect-error
assert<Expect<OddNumber<5>,1>>() // false, fail test

playground

as expected, it does not work.

so how can we fix it?

Actually the hint is already out there: non naked type parameters.

the solution is to convert the naked type parameter to the non-naked type parameter and the safest way is by turning them into arrays.

type Expect<T, U>= T[] extends U[] ? U[] extends T[] ? true : false : false

let’s try again:

playground

verify again with the example from part 1

playground

great both of them are working!

this is the end of part 2, in part 3 (not yet ready) we will go through more edge cases and further refine our Expect type

--

--

Acid Coder

Typescript Zombie. Youtube Pikachu On Acid. (Unrelated to programming but by watching it you become a good developer overnight)