microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

Home Page:https://www.typescriptlang.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Strange boolean-discriminant narrowing with strictNullChecks off

gcnew opened this issue · comments

TypeScript Version: nightly (2.1.0-dev.20160826)

Code

type Result = { success: true }
            | { success: false, error: string }

function handleError(res: Result) {
    if (res.success === true) {
        return;
    }

    res.error; // OK
}

function handleError2(res: Result) {
    if (res.success !== false) {
        return;
    }

    res.error; // OK
}

function handleError3(res: Result) {
    if (res.success) {
        return;
    }

    res.error; // Property 'error' does not exist on type 'Result'
               // but should be OK
}

Expected behavior:
All three cases should behave the same.

This is correct. We don't know that res.success isn't undefined or null, which would mean it's possibly not false and therefore not { success: false, error: string }

@RyanCavanaugh While you are technically right (and I was thinking the same), when strictNullChecks is off null and undefined are not modelled at all and such differences don't surface in other situations. For example handleError has exactly the same semantics but compiles just fine. I think the behaviour should be consistent or at least the error message should be more descriptive.

what is the expected behavior?

@DanielRosenwasser I have the same question

This is correct. We don't know that res.success isn't undefined or null, which would mean it's possibly not false and therefore not { success: false, error: string }

I'm wondering why the if (res.success === true) { example above should compile then, because couldn't success still be undefined or null in the else block?

Hey, I just wanted to point out that this issue now has additional relevance in TypeScript 3.6 due to the addition of the Generator type and the new IteratorResult type. I was writing some code with generators, expecting I could use while(!result.done) to loop over the case where the generator was not done, but found in fact I had to use while(result.done === false). Might be a reason to revisit this?

commented

I have experienced similar behaviour, and the following code helped me to work around this limitation:

(Assuming we have foo with the discriminating boolean-property "discBool" and the optional property "optionalProperty" which is only present in one of the two sub-types of foo's super-type. In other words: If discBool is false, optionalProperty is present, if discBool is true, optionalProperty is not present in foo)

switch (foo.discBool) {
  case false:
    doSomething(foo.optionalProperty);
}

while this code didn't work:

if (!foo.discBool) {
  doSomething(foo.optionalProperty); // Error here
}