You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A variable of type never should allow anything to be done with it #13803
My understanding of never is that it's the bottom type, and therefore a subtype/intersection of all other types. As such, shouldn't the following type check?
functionf(foo: never){returnfoo.bar// Type error: Property 'bar' does not exist on type 'never'.}
Since we can never actually obtain a value of type never as long as we haven't subverted the type system somewhere, there's nothing wrong in accessing arbitrary properties on it. From absurdity, anything follows, right?
One can already write this function, which type checks:
changed the title [-]A variable of type never should allow anything[/-][+]A variable of type never should allow anything to be done with it[/+]on Feb 1, 2017
there's nothing wrong in accessing arbitrary properties on it
Conversely, accessing properties on a value that will never be seen is a big red flag. What is gained from this change?
never more often shows up when people "accidently" exhaust a union type, rather than the very rare case of declaring something of type never. How would we report errors in that situation if never were effectively any ?
never more often shows up when people "accidently" exhaust a union type, rather than the very rare case of declaring something of type never. How would we report errors in that situation if never were effectively any ?
Shouldn't the situation in which a union type has been "accidentally exhausted" result in unreachable code errors, regardless of whether one tries to access properties of a variable whose type has been narrowed to never? For example:
if(false)console.log('hello')
Here the console.log is rightly flagged as Unreachable code detected. However in this situation:
even though the console.log is unreachable, it's not currently flagged as such. Only if one tries to make use of x.length or somesuch in the final branch will it be flagged as an error currently. So making property accesses on never a type error just for this case seems like a hack which doesn't fully solve the problem it's supposed to.
No one would want the exhaustion of a union to result in an error to result in an unreachable code warning, because unreachable code warnings have no way to be silenced, but it's still very common to write Debug.fail or throw or other sorts of code in those positions. I mean, what you're basically asking for there is for the declaration of f(x: never) to be itself an error, since by your own argument that function can't be called.
The difference between our syntactic unreachability analysis and the never type is that we are 100% confident in the unreachability of if (false) console.log('hello');. We can't be 100% confident about f(x: 'a' | 'b' | 'c') because non-TS code exists, any exists, type assertions exist, etc.
In Flow bottom works as upper bound of any, i.e. you can do anything with it, but can't directly assign anything to it.
Here is a practical example why it's useful:
typeUnion=|{t: 'Foo'}|{t: 'Bar'}declarefunctiontest(): Union;constx=test();switch(x.t){case'Foo':
console.log('This is Foo');break;case'Bar':
console.log('This is Bar');break;default:
console.log(`Unknown type: ${x.t}`);}
While default case is unreachable from type checker's point of view, in real life it still might happen because of incorrect typings or something like that
Anecdotally, when we added the never type, it found about a dozen bugs in our partner codebases where people did access members of things of type nevernot realizing the code was unreachable (i.e. there was a mistake in the logic of the function). It'd be a shame to have missed those bugs for the sake of removing a type assertion in expected-unreachable code.
typeUnion=|{t: 'Foo'}|{t: 'Bar'}declarefunctiontest(): Union;functionfn(x: number){}constx=test();switch(x.t){case'Foo':
console.log('This is Foo');break;case'Bar':
console.log('This is Bar');break;default:
// No error?constwat=x.flarp.substring(3);// No error?fn(wat);}
I mean, the big selling point is supposed to be a sound type system, but you write code that should be unreachable (oops?) and then access an undeclared property (oops again?) and pass that to an explicitly-typed function (oops?) with no warnings? Really? Why shouldn't I have to acknowledge in some form that I'm doing stuff with a value that I know is outside of its declared domain? Instead it's effectively any and the typechecker is silently letting me do whatever I want. That seems harmful, not helpful.
Notice that 2 out of your 3 oopses are not caught by current TypeScript. This is why I think an unreachable code warning would be ideal, if you could disable it with a pragma to say "I acknowledge that I'm in untyped land here, just covering my bases".
I mean, the big selling point is supposed to be a sound type system,
I'd argue that this behaviour is both sound and consistent from type system's point of view. If code is considered unreachable then its content is irrelevant
Formally ∀X: ⊥ <: X, hence Γ ⊢ e : ⊥ -> Γ ⊢ e : X. This means that every operation on some type T must be allowed on bottom type
I often write a switch statement that has a case for each option of a union. In the default branch, I prefer to throw an execption that has the swiched value in its message, so that debugging is easier. This is the same as Vladimir Kurchatkin (@vkurchatkin)'s flow example. Currently, this pattern requires a type assertion.
switch (node.kind) {
case 'Field':
//...
case 'FragmentSpread':
case 'InlineSpread':
//...
default:
throw new Error(`Unexpected node kind: ${(<any>node).kind}`);
}
I don't think reverting to something like any for node would be good, because then, the code completion and name checkinf for kind would be gone. I would prefer to revert any narrowing done by the switch statement and end up with the original type at the beginning - then I could access kind normally.
So we get bugs like #15079 all the time (that one doesn't produce a never, but many do). "I don't have to write a type assertion in unreachable code" is not obviously a feature because people often write code that they don't realize is unreachable.
Activity
[-]A variable of type never should allow anything[/-][+]A variable of type never should allow anything to be done with it[/+]RyanCavanaugh commentedon Feb 1, 2017
Conversely, accessing properties on a value that will never be seen is a big red flag. What is gained from this change?
nevermore often shows up when people "accidently" exhaust a union type, rather than the very rare case of declaring something of typenever. How would we report errors in that situation ifneverwere effectivelyany?pelotom commentedon Feb 1, 2017
Shouldn't the situation in which a union type has been "accidentally exhausted" result in unreachable code errors, regardless of whether one tries to access properties of a variable whose type has been narrowed to
never? For example:Here the
console.logis rightly flagged asUnreachable code detected. However in this situation:even though the
console.logis unreachable, it's not currently flagged as such. Only if one tries to make use ofx.lengthor somesuch in the final branch will it be flagged as an error currently. So making property accesses onnevera type error just for this case seems like a hack which doesn't fully solve the problem it's supposed to.RyanCavanaugh commentedon Feb 1, 2017
No one would want the exhaustion of a union to result in an error to result in an unreachable code warning, because unreachable code warnings have no way to be silenced, but it's still very common to write
Debug.failorthrowor other sorts of code in those positions. I mean, what you're basically asking for there is for the declaration off(x: never)to be itself an error, since by your own argument that function can't be called.The difference between our syntactic unreachability analysis and the
nevertype is that we are 100% confident in the unreachability ofif (false) console.log('hello');. We can't be 100% confident aboutf(x: 'a' | 'b' | 'c')because non-TS code exists,anyexists, type assertions exist, etc.vkurchatkin commentedon Feb 1, 2017
In Flow bottom works as upper bound of
any, i.e. you can do anything with it, but can't directly assign anything to it.Here is a practical example why it's useful:
While default case is unreachable from type checker's point of view, in real life it still might happen because of incorrect typings or something like that
pelotom commentedon Feb 1, 2017
Ryan Cavanaugh (@RyanCavanaugh) fair point. I still think disallowing property accesses on
neveris a hack which hurts as much as it helps, as Vladimir Kurchatkin (@vkurchatkin)'s example shows.RyanCavanaugh commentedon Feb 1, 2017
Anecdotally, when we added the
nevertype, it found about a dozen bugs in our partner codebases where people did access members of things of typenevernot realizing the code was unreachable (i.e. there was a mistake in the logic of the function). It'd be a shame to have missed those bugs for the sake of removing a type assertion in expected-unreachable code.RyanCavanaugh commentedon Feb 1, 2017
The Flow example is extremely surprising to me.
I mean, the big selling point is supposed to be a sound type system, but you write code that should be unreachable (oops?) and then access an undeclared property (oops again?) and pass that to an explicitly-typed function (oops?) with no warnings? Really? Why shouldn't I have to acknowledge in some form that I'm doing stuff with a value that I know is outside of its declared domain? Instead it's effectively
anyand the typechecker is silently letting me do whatever I want. That seems harmful, not helpful.pelotom commentedon Feb 1, 2017
Notice that 2 out of your 3 oopses are not caught by current TypeScript. This is why I think an unreachable code warning would be ideal, if you could disable it with a pragma to say "I acknowledge that I'm in untyped land here, just covering my bases".
vkurchatkin commentedon Feb 1, 2017
I'd argue that this behaviour is both sound and consistent from type system's point of view. If code is considered unreachable then its content is irrelevant
Formally
∀X: ⊥ <: X, henceΓ ⊢ e : ⊥->Γ ⊢ e : X. This means that every operation on some type T must be allowed on bottom typeYogu commentedon Jun 20, 2017
I often write a
switchstatement that has acasefor each option of a union. In thedefaultbranch, I prefer to throw an execption that has theswiched value in its message, so that debugging is easier. This is the same as Vladimir Kurchatkin (@vkurchatkin)'s flow example. Currently, this pattern requires a type assertion.I don't think reverting to something like
anyfornodewould be good, because then, the code completion and name checkinf forkindwould be gone. I would prefer to revert any narrowing done by theswitchstatement and end up with the original type at the beginning - then I could accesskindnormally.RyanCavanaugh commentedon Jun 20, 2017
So we get bugs like #15079 all the time (that one doesn't produce a
never, but many do). "I don't have to write a type assertion in unreachable code" is not obviously a feature because people often write code that they don't realize is unreachable.I'd recommend the pattern outlined here https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript if you want type-assertion-free code.