Assignability of never
In TS, if type T extends U, then it follows that T is assignable to U.
With this in mind, I noticed some counter-intuitive behaviour with the never
and unknown types:
type X1 = never extends string ? true : false; // Returns true
type X2 = unknown extends string ? true : false; // Returns false
This would suggest that never is assignable to string but unknown is not
assignable to string.
const x1: string = "" as never;
// @ts-expect-error
const x2: string = "" as unknown;
My understanding came from this
GitHub discussion.
It seems the way to think about this is never is the most 'specific' type,
otherwise known as the 'bottom' type or empty set, and unknown is the most
broad type (besides any). So it would follow that you couldn't assign
unknown to string as unknown could be something else, like a boolean.
But how does assigning never to string make sense? What does this even mean?
I found that thinking about every types as unions helps with reasoning about
this. You could consider string to be a union of 1 type. You can also consider
never to be the empty set. So it should follow that string | never is the
same as string:
const x: string = "" as string | never;
So this would suggest either string or never is assignable to string.
const x: string = "" as never;
In fact, never seems to be assignable to any type.
The thing that is hard to grok is that this would never happen at runtime thus
the weird cast to never. But I guess if you think about it purely from a type
system or set theory perspective, it makes sense that this should type check.
I got nerd sniped into investigating this because I was trying to figure out the
bottom type of a function: how do you write the generic type T that represents
all functions? This is relevant in a type challenge I was doing to implement the
ReturnType utility without using the built-in one.
I initially tried something like this:
type MyReturnType<T extends (...args: unknown[]) => unknown> = T extends (
...args: unknown[]
) => infer R
? R
: never;
But found out this would fail if T was a function with arguments other than
unknown or any. What I needed to find the bottom function type was to make
sure I was using the bottom type in the args:
type MyReturnType<T extends (...args: never[]) => unknown> = T extends (
...args: never[]
) => infer R
? R
: never;