Truthy Falsy in 0.9.5

Topics: General
Dec 6, 2013 at 2:42 PM
Edited Dec 6, 2013 at 2:51 PM
function test(b: boolean, d: Date) {
    var r1: boolean = b || d; // "Cannot convert '{}' to 'boolean'."
    var r2: boolean = b || <boolean>d; // Bad restriction.  "Type 'Boolean' is 
                                       // missing property 'toDateString' from 
                                       // type 'Date'" (and fix Casing of boolean)
    var r3: boolean = <boolean>(b || d); // OK but dumb
    var r4: boolean = !!(b || d); // OK but dumb - requires unecessary javascript - 
                                  // and if `!` can take it, why can't an 
                                  // expecting-boolean expression take it?
    var r5 : boolean = (b || d) ? true : false; // same here, illustrating 
                                                // `? :` condition, parens optional
}
Coordinator
Dec 6, 2013 at 4:58 PM
Edited Dec 6, 2013 at 5:17 PM
The type of the || operator is the "best common type" of the two operands. This is the same procedure we use for determining the type of array literals and other places where the types of several expressions are "combined" into one type. When there is no best common type, as there is here, the result is { }, the empty type.

r1 should hopefully be what you expect, since the result of the expression could either be a boolean (true) or a Date.

In r2, the type assertion is disallowed because boolean is not assignable to Date and Date is not assignable to boolean. The idea here is that type assertions should at least be plausible; if you really have some value of a completely different type, you can use <boolean><any>d instead (though ideally you would just fix the typing of whatever gave you that expression).

r3 is valid because boolean is assignable to {}. It's an interesting contrast to r2 because essentially the compiler is saying "You produced a value we can't reason about, so I'm not going to try to enforce any plausibility of that type assertion".

r4 is not dumb because it has an actual runtime implication -- if b is false, r4 be guaranteed to be a boolean value. This is idiomatic JavaScript and it has practical implications and uses.

r5 takes advantage of the fact that any type of expression is valid in a control flow test expression (the alternative would be far too annoying).
Dec 6, 2013 at 5:24 PM
r2 still seems like a mistake to me. boolean doesn't have any contractual obligations to fill. And even if it did, it seems to me that the error should be that the other way around: "Type 'Date' is missing property 'negate()' from type 'boolean'" since we're casting AS boolean, the original type has to be compatible only in that direction. That is, if I'm explicitly casting as boolean, why would I expect to be able to use .toDateString()? And I /would/ be upset if it were missing boolean's 'negate()'. But I'm not aware of any .negate()-like members in boolean so I feel like it should pass without error in this situation.

What I'm saying about r4 is that while this is perhaps a least-disruptive syntax (beaten only by b || !!d), it is only required to satisfy the type system but has a wasteful runtime overhead. Negligable, I'm sure, but that's irrelevant.

I understand why this behavior is happening, but I guess I'm suggesting that the type system should take into account the special case of that a non-boolean expression will be implicitly cast to boolean one in cases where only boolean is allowed - just as it does for operators ! and ? :.

I believe that boolean is as narrow a type as you can get, so there's no danger in treating a non-boolean type as a boolean - like I was saying above, there's no .negate() that the type system would let you use if the value were improperly cast and leaked into other areas of the code. So even if we expected a boolean and at runtime got an Array, or undefined or whatever, the javascript engine would handle it just as you expect it would and just as the type system expected it to.
Coordinator
Dec 6, 2013 at 5:38 PM
I understand why this behavior is happening, but I guess I'm suggesting that the type system should take into account the special case of that a non-boolean expression will be implicitly cast to boolean one in cases where only boolean is allowed
boolean does have a specific contract, which is that it's the only type that will ever be === false or === true. Consider this code:
function makeVisible(visible: boolean) {
    if (visible === true) {
        show();
    } else {
        hide();
    }
}

makeVisible(1); // Ends up calling hide(), not show()
This sort of error is very common and very worth catching.

If you want to represent a type that is going to be used without comparison in a truthiness test (e.g. x in if(x) {...}), you can use any, {} or, if you want to capture the intent more precisely, you could even write something like this:
// Represents a truthy or falsy value
interface booly { }

function makeVisible(visible: booly) {
    if (visible) {
        show();
    } else {
        hide();
    }
}
Dec 6, 2013 at 5:45 PM
Yeah, that's a good supporting example and explanation. Thanks. It's not dumb after all.

What about the r2 error message though? Should it not be that other way around?

Thanks.
Coordinator
Dec 6, 2013 at 5:55 PM
Edited Dec 6, 2013 at 5:55 PM
The error is confusing, but technically correct (i.e. the worst kind of correct when issuing errors) because we actually would consider the type assertion to be valid if Boolean had all the members of Date.

We consider an assertion of type A from type B to be valid if A is assignable to B or B is assignable to A. The full explanation requires reading spec sections 3.8.1 and 3.8.4, but long story short: the check fails in the first direction where we treat the boolean as a primitive type because primitive and object types are never assignment compatible, then fails in the other direction where we treat the boolean as an object type which lacks the members of Date.
Dec 6, 2013 at 7:22 PM
Ok cool. Thanks.