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
nevertype 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
nevertype 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|neverHowever, 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|numberis 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 ofnumbers.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
nevers?What values are of type
never? Well… No values are of typenever. The set ofneveris an empty set. It has no values. No elements.And the empty set is a subset of… Every other set. The set of
numbers. The set ofstrings. Etc.This is also why
never|numberis the same type asnumber. And also whynever|<any-other-type>is the same as<any-other-type>.Since, given a set
S, the union ofSand the empty set is justSBeing 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.tscontainsdeclare 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
namevariable, 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
nameas a name”. But I’m wondering if there are better solutions for this.Maybe the global
namecould be declared differently? As a string? Orunknown?Maybe disallow using variables of type
neverwhere 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
neveris assignable to everything so the globalnamebeing declared asneverdoesn’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 toneverfor convenience.@mjomble The explanation there is because
booleanis 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 thisnonetype.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.