The Type of a `type` in Typescript
Tags: typescript
Published: 2020-03-16 21:42:16

I'll present four puzzles and two solutions about TypeScript `class`es and `type`s.

> The cover image is from [another article](https://medium.com/@jiwonjessicakim/russells-paradox-f8897).

These are just puzzles I find fun, but reading this might have the happy side effect of getting you out of a TS jam in the future. All four puzzles have come up in real-life situations that I've been asked about.

## Puzzle 1

TypeScript has a neat feature, type-level `typeof`. It's probably inspired by the distinct value-level JS feature also called `typeof`:

```js
typeof null;       // "object" 
typeof class A {}; // "function"
```

TypeScript's type-level `typeof thing` gives you an alias for the type of the variable `thing`:

```ts
const thing = 3 * 2;
type Num = typeof thing;

const y: Num = 1; // OK
```

But what is the type of `Num`?

```ts
type TypeType = typeof Num; // error: 'Num' only refers to a type, but is being used as a value here
```

TypeScript won't even let us ask the question.

> Coq, Agda, and Idris all let you ask this type of question, but it is un-askable in TypeScript.

So we've disovered this rule: in TS, types don't have types. Fair enough. So what's going on in the following example?

```ts
class A {}
const a: A = new A();
type AType = typeof A;
```

The second line shows us that `A` is a type (we are using it to say that `a` is of type `A`).
Then the third line shows us that we can take the type of `A`. So it looks like we've taken the type of a type!

Puzzle 1 is: "How can types both *have* and *not have* types in TypeScript?


## Puzzle 2

Puzzle 2 is: How can `AA` be both equal and not equal to `A` below?

```ts
type AA = A;
// if AA and A really are the same type,
// then we'd expect them to behave the same way
type works = typeof A;        // OK
type doesNotWork = typeof AA; // Error
```

When reading the example above, bear in mind the principle of [Identity of Indiscernables](https://en.wikipedia.org/wiki/Identity_of_indiscernibles). If `A` and `AA` really are equal, then we'd expect to be able to use `AA` wherever we use `A`. But the last two lines of the example show a case where `A` is allowed but `AA` is not.


## Solution to Puzzles 1 and 2

At this point, there are two puzzles:
1. How could `AA` be both equal and not equal to `A`?
2. How can types both *have* and *not have* types in TypeScript?

The puzzles are just illusions caused by the interaction of two TS features:
- TS has two worlds of things: the World of Values and the World of Types
- `class` declarations introduce both a type and a value

Things like `type` aliases and `interface`s live in the World of Types. Variables declared with `const`, `let` and `var` exist in the World of Values. You're allowed to reuse names in the same scope as long as they refer to things in different worlds:

```ts
type Thing = number;
const Thing = "hello"; // no error
```

But two things in the same world cannot have the same spelling.

```ts
const thing2 = 3;
const thing2 = {}; // error: Cannot re-declare block-scoped variable "thing2"
```

> Ignore apparent violations of the rule for `interface`, `namespace`, and `enum`. This is the dread ["declaration merging"](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) feature I [wrote about earlier](https://medium.com/@maxheiber/alternatives-to-typescript-enums-50e4c16600b1).

As you saw above, we can use `type` to name something in the World of Types and `const` to name something in the World of Values. But a `class` declarations is magical: it *simultaneously* names something in the world of values and names another thing in the world of types!

The next example illustrates this double-declaration behavior. Note that:
- the type `C` is the type of *instances* of `C`
- Applying the type world's `typeof` operator to `C` gives you the type of the value-land `C`, which is a constructor function.

```ts
class C { a = 1 };
type CInstance = C;    // alias the `C` from the World of Types
const Constructor = C; // alias the `C` from the World of Values

const c: C = new C();
const c2: CInstance = new C();
const cons: typeof C = Constructor;

// The next line errors, because CInstance does not have the same dual-nature as `C`
// CInstance is *only* a type, so you cannot calculate its type
const cons2: typeof CInstance = Constructor;
```

Two puzzles solved!

Consider again an example like this:

```ts
class A {}
type AA = A;
type Z = typeof A;   // OK
type ZZ = typeof AA; // Error

```

Puzzle 1 was "How could `AA` be both equal and not equal to `A`?".

The answer is that there are two `A`s: one in the world of types and one in the world of values. `AA` is equal to the `A` in the world of types, but not equal to the `A` in the world of values.

Puzzle 2 was "How can types both *have* and *not have* types in TypeScript?"

The answer is that we can never ask about the types of types in TypeScript. The seeming-violation of this rule was only because we were dealing with two `A`s: a type-level `A` and a value-level `A`. We can ask for the `typeof` the value-level A, but TS won't let us ask about the type of the type-level `A` or of any of its aliases.

## Puzzle 3

Puzzle 3 is: why are there no errors in the code example below?

```ts
class Klass {};
const konstructor = Klass;
const obj1: Klass = {};
const func: typeof Klass = konstructor;
const obj2: Klass = konstructor; // why no error here?
```

## Puzzle 4

Puzzle 4 is: how can we correctly-type a function that returns a class?

For example, is there a way to get things like this to compile?

```ts
const MyClass = createClass({});
const m: MyClass = new MyClass();
```