TypeScript: Why is never assignable to every type?
I understand that the issue tracker is not for questions, which is why I’ve already asked this on StackOverflow:
https://stackoverflow.com/questions/53540282/why-is-never-assignable-to-every-type
But as I haven’t gotten an answer to the main question in almost two weeks, I thought I’d try here as well:
The TypeScript documentation says that
The
never
type is a subtype of, and assignable to, every type
but doesn’t mention why.
Intuitively, I would expect code like this to fail:
const useString = (str: string) => console.log('This is definitely a string:', str)
const useNever = (not_a_string: never) => useString(not_a_string)
but there are no errors, because any never
value is considered a valid string.
Is this intentional? If yes, then why? 😃
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 8
- Comments: 16 (5 by maintainers)
The
never
type is meant for functions that never return.A function that throws an exception is a function that never returns.
A function that gets stuck in an infinite loop also has a return type of
never
.This function returns
number|never
However, if
russianRoulette()
does return a value, it’ll return a value of typenumber
.This also works,
So, we can see that a return value of type
never|number
is assignable to a variable of typenumber
.An intuitive way to think about assignability is to think about sets and subsets.
The set of
{1,2,3}
is a subset of the set ofnumber
s.Therefore, the following is allowed,
The set of
{1,2,3}
is not a subset of the set{2,3,4}
.Therefore, the following is not allowed,
So… What is the set of
never
s?What values are of type
never
? Well… No values are of typenever
. The set ofnever
is an empty set. It has no values. No elements.And the empty set is a subset of… Every other set. The set of
number
s. The set ofstring
s. Etc.This is also why
never|number
is the same type asnumber
. And also whynever|<any-other-type>
is the same as<any-other-type>
.Since, given a set
S
, the union ofS
and the empty set is justS
Being the empty set, no values are assignable to a variable of type
never
.These are very good answers. I also finally got a proper answer on SO (probably thanks to this issue) that mentions the logical principle of explosion.
And in theory, this all makes sense now.
…but… 😃
In practice, things like this can happen:
lib.dom.d.ts
containsdeclare const name: never;
Which means that any TypeScript code targeting a browser has a global “impossible variable” with a very common name. Namely,
name
.And due to the principle of explosion, if an impossible variable exists, everything false might as well be true 😃
For a more concrete example, let’s say a developer makes a mistake like this:
This is valid TypeScript, but throws an error at runtime.
Or perhaps you had a local
name
variable, then removed it and expected TypeScript to give you errors about any remaining references to it. But all those practically broken references are still theoretically valid, because never is everything 😃This has resulted in actual bugs for me on several occasions. So far, my workaround has been “Never use
name
as a name”. But I’m wondering if there are better solutions for this.Maybe the global
name
could be declared differently? As a string? Orunknown
?Maybe disallow using variables of type
never
where variables of some other type are expected? In theory, this would be wrong. But in practice, maybe the benefits would outweigh the costs?Alternatively, I guess a TSLint rule could be created that checks for this.
I think we’ve lost sight of the original goal of this issue - the issue was specifically that
never
is assignable to everything so the globalname
being declared asnever
doesn’t actually prevent all errors. It prevents it being assigned to, but doesn’t catch cases where it’s used in an expression.The problem here is that there’s no type that means “you can’t assign to OR from it”—even if you make an impossible intersection that doesn’t collapse to
never
, it will nonetheless act likenever
. Under structural typing, any type which is effectively an empty set is unambiguously a subtype of everything.If you want to collapse “impossible” intersections to
never
, union them withnever
:Original:
All never:
This was decided as a compromise between the people who used
& "brand"
for “branding” their types (fake nominalism) and the people who wanted these to collapse tonever
for convenience.@mjomble The explanation there is because
boolean
is a union typetrue | false
, and empty intersections only get simplified if they appear within a union. For example:The point is moot though because those intersection types are assignable to lots of things from the rules for intersections alone (independently of whether they denote
never
), so they don’t function like thisnone
type.I now looked into this a bit and found some older issues.
Most of them are pointing to https://github.com/Microsoft/TypeScript/issues/15424#issuecomment-301207034
However, as has been pointed out in some of the other issues, you do not actually get an error from using a
never
.