microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.69k stars 12.44k forks source link

Bug: Boolean type literals misbehaving #10432

Closed benjamin-hodgson closed 8 years ago

benjamin-hodgson commented 8 years ago

TypeScript Version: nightly (2.1.0-dev.20160818)

Code I lifted this from Anders's final example in https://github.com/Microsoft/TypeScript/pull/9407#issue-162831239.

type Result<T> = { success: true, value: T } | { success: false };

function foo(): Result<number> {
    if (someTest()) {
        return { success: true, value: 42 };
    }
    else {
        return { success: false };
    }
}
function someTest(): Boolean { return true; }

Expected behavior: Type-checks OK

Actual behavior: Type error:

result.ts(5,16): error TS2322: Type '{ success: boolean; value: number; }' is not assignable to type 'Result<number>'.
  Type '{ success: boolean; value: number; }' is not assignable to type '{ success: false; }'.
    Types of property 'success' are incompatible.
      Type 'boolean' is not assignable to type 'false'.
result.ts(8,16): error TS2322: Type '{ success: boolean; }' is not assignable to type 'Result<number>'.
  Type '{ success: boolean; }' is not assignable to type '{ success: false; }'.
    Types of property 'success' are incompatible.
      Type 'boolean' is not assignable to type 'false'.

If I add manual type annotations (true as true, false as false) to the success assignments it works:

function foo(): Result<number> {
    if (someTest()) {
        return { success: true as true, value: 42 };
    }
    else {
        return { success: false as false };
    }
}

The bug only affects Boolean literals. If I use enum, number, or string type literals it works - for example:

type Result<T> = { success: 1, value: T } | { success: 0 };

function foo(): Result<number> {
    if (someTest()) {
        return { success: 1, value: 42 };
    }
    else {
        return { success: 0 };
    }
}
bchenSyd commented 7 years ago

not fully fixed....

type Result<T> = { success: true, value: T } | { success: false };

function foo(): Result<number> {
    if (someTest()) {
        return { success: true, value: 42 }; //OK
       return Object.assign({}, { success: true, value: 42 })  // type 'boolean' is not assignable to type 'true'
    }

}
RyanCavanaugh commented 7 years ago

@bochen2014 I don't see how we could fix that case. In general we don't assume that functions won't modify a type within its own domain, and the true value gets quickly widened to boolean here because it's not used in a position where it's known to not be mutated.

bchenSyd commented 7 years ago

hi Ryan, thanks for your reply. Just so you know that I've walked around the issue by

return {
success: true, 
value :42,
...someObject
}

which does the same as Object.assign