-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Allowing multiple "never" types to co-exist compromises type safety #50581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is the expected behaviour of Distributive Conditional Types. To prevent distributiveness you can wrap your types in brackets: type IsNever<T> = [T] extends [never] ? "YES" : "NO";
This is working by design.
This is a duplicate of #50185.
Such an array can absolutely exist. Here, an example: |
@MartinJohns Thank you for the hint regarding the brackets around type NeverTupleShouldBeNever = IsNever<[never]> // should probably be "YES", but it is "NO" I am also wondering what do you think about |
let x: ObjWithNeverValue = { value: alwaysThrows() }; // ok
let y: never = { value: alwaysThrows() }; // not ok, error
let z: never = alwaysThrows(); // ok The bottom type models the "return value" of functions that don't return normally. So you can treat it as anything you want, since the code that uses the value should always be unreachable. If you ever end up reading a value from something typed as This isn't just sleight of hand, either; it has mathematical and logical precedent. I wrote a more nerdy theoretical explanation in a different issue recently, see #49862 (comment) |
You can still construct objects with |
@MartinJohns Well, I didn't know that type assertions would allow that. It doesn't sounds very type-safe. Digging a little bit deeper, it seems like the following is also possible: function isNever(x: unknown): x is never {
return true;
}
const a = 123;
let x: string;
if (isNever(a)) {
x = a;
} which really doesn't sounds valid to me.
Would you mind sharing a practical example which requires this kind of union type to be used? Shouldn't a union of two |
Thank you for sharing the link. So yes, I am aware of all of that. And it only proves my point I guess. There shouldn't exist any values of type |
The entire purpose of type assertions is to work around the type system. Nothing else. Just to clarify, in the example that follows you used a type guard, not a type assertion.
The implementation of type guards is not checked against the return type annotation. This is whole different issue (too lazy to look it up), and completely unrelated to
I would personally not do it like this, the better option is to use a discriminated union. But if you have a union of different cases, and you want to ensure that only one of it matches, you need to use these
A union of two
An object having a property typed Another example, in this comment Ryan describes the type |
Correct. It working as a bottom type is contingent on the idea that you canβt legally produce a value of that type without subverting the type system (via a cast, e.g.). You can safely pretend itβs anything you want, so long as all the code that βholdsβ such a value remains unreachable. |
Having studied type theory for many years, I would simply like to point out that this is incorrect:
The initial object is only required to be unique up to isomorphism, not up to equality. In type systems like Coq or Agda, which have a very strong (and sound) type theoretical basis, it is very easy to produce different empty types, for instance defining two different data types with no constructors (or the type of equalities between 0 and 1, or the type of functions from a non-empty type to an empty type, and so on). Elements of such types are not assignable to other empty types, it's only possible to construct functions between those types. |
Well, the point is this doesn't seem the be the case: const a: { value: never } = {}; actually gives an error:
which seems to be contradicting your statement. Please have a look at this tsplayground. In fact "not having property" would be better represented by
Of course it's not. I wasn't suggesting that this should be the case. What I meant was that type guards with return type |
I don't see any incorrect behavior here. See also https://twitter.com/SeaRyanC/status/1326575516250329088 |
|
There's an infinite amount of nonsensical code you can write and only a finite amount of time to check for all of those constructs before people get bored of waiting for the checker to finish and stop using static type checkers altogether. Unless people are frequently going around writing |
Lemme summarize the above discussion:
|
Bug Report
π Search Terms
never, property, extends, intersection
π Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about "never"
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
None of the above types is recognized as
never
, even though they are all types that can have no legal values. The following comment from @RyanCavanaugh seems to confirm it:#43954 (comment)
This leads to a conclusion that TS allows multiple "never" types to co-exist. In type theory, this shouldn't happen because the initial object (if it exists) must be necessarily unique. Unfortunately, not recognizing that those types are identical leads to type safety being compromised in certain situations, e.g.
#50559
π Expected behavior
I think all these types should be recognized as equal to
never
. As such, no values of these types should be allowed to exist, as it the case of:See: #50559
In particular a value of type
never
should not be considered a legal return value of any function unless the return type is explicitly declared asnever
.The text was updated successfully, but these errors were encountered: