TypeScript Version: 3.8.0-dev.20200115
Search Terms: overlap boolean vs array condition
Code
function isAllowed() {
const myCondition = false;
if (myCondition === false) {
return [];
}
return true;
}
if (isAllowed()) {
// boolean and array will results in a "true" condition
console.log('allowed');
} else {
// never reached
console.log('not allowed');
}
Expected behavior:
This condition can produce unexpected results since the types 'boolean' and 'array' have no overlap (not really good but you get the idea)Actual behavior:
I won't say that it's a bug, but something that could be improved. I know that it's valid javascript but this a bug in the code that should/could be prevented by Typescript
Already fixable by
if (isAllowed() === true) but it's easy to not do it.Playground Link: http://www.typescriptlang.org/play/?ts=3.8.0-dev.20200115&ssl=1&ssc=1&pln=15&pc=1#code/GYVwdgxgLglg9mABDAzgQQDYbgdwKYAmAFAJSIDeAUIohAilIgLYCeAwggTLAogLyIoAJxB4A3NWTBERVhzBceSPisEi8ZKjRpC8UEEKQBtALoSaAX0qTd+w4mABDDCnGUrlGNKKpM2fMQkmpJ0YChwGHgAdNgA5kQA5M7+hAkkEhaIeC54FCH0EdFxiWBwjMm4qenulEA
nb: it's not really related to boolean and array, but that's an easy mistake. Maybe it can be covered by a strict check that would require if condition to always be compared with boolean
It took me a bit of time to wrap my head around this, but indeed, I would love if typescript could warn when a condition is both checking for:
null, non-undefined)Here's an example with number | null : https://www.typescriptlang.org/play/?ts=3.8.0-dev.20200115&ssl=1&ssc=1&pln=7&pc=2#code...
This has always been a thing to keep in mind with JavaScript, but having a strict flag forbidding using a condition which checks for both presence and value truthiness would be great.
The example in @bodinsamuel's first message goes one step further, because null and undefined are never even mentionned. The infered return type here is boolean | [].
The thing is boolean can be both truthy and falsy, whereas [] will always be truthy.
So overall, I feel like the warning that could be helpful would be if you're using, in a condition, a union type that contains a mix of:
falsy types (null, undefined)boolean, number, string)truthy typesIf this was considered, this would probably need to be a new strict flag.
So thinking these through (this is my gut check, please disagree):
boolean | undefined - bad (conflates false and undefined)boolean | object - bad (conflates true and {})number | undefined - bad (conflates 0 and undefined)object | number - bad? (conflates 1 and {})boolean | number - bad? (conflates on both sides)object | undefined - good (falsiness is unambiguous)The rule I can backport on to this intuition is "If there is more than one truthy value or more than one falsy value, those values should be of the same type".
What's your intuition?
Is boolean | string bad? If so, how can you model JavaScript code typed like boolean | "auto"?
What's a reasonable spelling of blah that makes this code make sense?
declare function blah(): boolean | string;
function fn() {
if (blah()) {
// Do one thing
} else {
// Do something else
}
}
The rule I can backport on to this intuition is "If there is more than one truthy value or more than one falsy value, those values should be of the same type".
I like that, feels like a fair and understandable warning. At least for a beginning; it seems super useful.
object | undefined - good (falsiness is unambiguous)
This is where I would go even more strict, I would considered it bad as it results to always true on one side and always false on the other. But I just learn that {} === undefined is considered valid in Typescript. I suppose because an object is any ?
I would apply to the same rule with boolean | string or boolean |聽array, if there one side of the type is always truthy or falsy, it should trigger a warning (unless there is an actual comparison in the if)
Adding salt to my own issue, while searching for more use case I discovered there is an eslint rule that basically do that (or almost).
https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md
It can interpreted in both way:
This is where I would go even more strict, I would considered it bad as it results to always true on one side and always false on the other.
I don't understand the point of that. Code like this is extremely idiomatic and poses no risk of confusion:
declare function getLoggedInUser(): User | undefined;
const user = getLoggedInUser();
if (user) {
console.log(`Hello, ${user.name}`);
} else {
console.log(`Please log in`);
}
@RyanCavanaugh I think boolean | "string-literal" is the interesting case.
declare function blah(): boolean | "auto";
function fn() {
let x = blah();
if (x) {
return x === "auto" ? detectCondition() : true;
} else {
return false;
}
}
Could it have checked for !x instead to avoid some extra nesting? Yes, but it's feasible code.
In my experience, anyone attempting to do something like this:
const myArray = functionThatReturnsVariableLengthArray();
if (myArray) {
// do stuff
}
are always making a mistake. The following is their intention:
if (myArray.length > 0) {
I don't think it ever makes sense to evaluate an array (or any object) directly as a boolean.
Mistake falsy:
number(0 / NaN)
string("")
bigint(0n)
If the "mistake falsy" type appears in the if the condition with boolean undefined or null, raise a type error.
Therefore my conclusion:
if (x as string | undefined)
// Error: The speical value "" in type "string" can also be falsy in this context.
if (x as number)
// Ok
if (x as number | undefined)
// Error: The speical value 0 or NaN in type "number" can also be falsy in this context.
if (x as bigint | undefined)
// Error: The speical value 0n in type "bigint" can also be falsy in this context.
Mistake truthy type:
Any type excludes mistake falsy type and null, boolean and undefined
If mistake truthy type appears in the if condition, treat it as true.
if (x as any[])
// Error: This condition will always be true since type any[] has no overlap with false.
if (x as object)
// Error: This condition will always be true since type object has no overlap with false.
if (x as object | undefined)
// OK.
Most helpful comment
So thinking these through (this is my gut check, please disagree):
boolean | undefined- bad (conflatesfalseandundefined)boolean | object- bad (conflatestrueand{})number | undefined- bad (conflates0andundefined)object | number- bad? (conflates1and{})boolean | number- bad? (conflates on both sides)object | undefined- good (falsiness is unambiguous)The rule I can backport on to this intuition is "If there is more than one truthy value or more than one falsy value, those values should be of the same type".
What's your intuition?