-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Narrowing behaviour differs with guard order #7045
Comments
Consider: function doSomething(x: Thing|string) {
if (typeof x === 'string') {
//...
} else if (isActuallyThing(x)) {
//...
} else {
x // type of x here is Thing <== WHY NOT Thing|string?
}
}
//Is really the same thing as
function doSomething(x: Thing|string) {
if (typeof x === 'string') {
//x is a string, dur
} else {
//x must be a Thing here
if (isActuallyThing(x)) {
//bit of a redundant guard - we're no longer in a union type so no narrowing gets done
} else {
x // type of x here is Thing <== WHY NOT Thing|string?
// also not in a union type, the above guard does nothing
}
}
} At least that's why it currently works as it does - you could certainly argue that if we aren't in a union anymore, we should still remove types if possible just so we can revert to the original type if possible. |
@weswigham applying the same logic to function sameButDifferent(x: Thing|string) {
if (isActuallyThing(x)) {
//...
}
else if (typeof x === 'string') {
//...
} else {
x // type of x here is Thing|string
}
}
//Is really the same thing as
function sameButDifferent(x: Thing|string) {
if (isActuallyThing(x)) {
//x is a Thing, dur
} else {
//x must be a string here
if (typeof x === 'string') {
//bit of a redundant guard - we're no longer in a union type so no narrowing gets done
} else {
x // type of x here is Thing|string <== WHY NOT string?
// type widened back out to Thing|string - different behaviour to doSomething but why?
}
}
} So why not just |
@yortus how is |
@tinganho I defined it as |
The issue here is not about ordder, as much as it about behaviour. the two type guards do not behave the same way; the typeof type guards are much "stronger", the user defined and instance of are "weaker", so: if (typeof x === "string") {
x // string
if (isThing(x)) {
x // still string
}
}
else {
x // thing
if (typeof x === "string") {
x /// string | Thing
}
} |
I should add the rational is, that at runtime it is impossible to get a value whose typeof is both "string" and "number", but it is possible to get something that is compatible with both types |
Right, you can have a value that is both if (typeof x === "string") {
x // string
if (isThing(x)) {
x // Thing&string (but currently string)
}
}
else {
x // Thing
if (typeof x === "string") {
x // Thing&string (but currently Thing|string)
}
} The current narrowing behaviour leads to compile-time errors on code that is valid and works at runtime. |
Whilst looking at @RyanCavanaugh's example code in this comment I noticed odd narrowing behaviour depending how the type guards are arranged. Is this expected?
The text was updated successfully, but these errors were encountered: