Skip to content

Literal type false unexpectedly widens into type boolean #25474

Closed
@cliffkoh

Description

@cliffkoh

TypeScript Version: 3.0.0-dev.20180705

Search Terms: false/true loosened/expanded/loses specificity into boolean

Code

type Foo = FooBase | FooArray;
type FooBase = string | false;
type FooArray = FooBase[];

declare let foo1: Foo;
declare let foo2: Foo;
foo1 = [...Array.isArray(foo2) ? foo2 : [foo2]];

Expected behavior:
No error.. In the else clause, [foo2], it retains string | false so Array<string | false>. Logically since everything is taking place in the typings layer, foo2 should still be strongly bounded to false and will never be true.

Actual behavior:
Errors. In the else clause, [foo2], it expanded/loosened to string | boolean so Array<string | boolean>.

tmp.ts:7:1 - error TS2322: Type '(string | boolean)[]' is not assignable to type 'Foo'.
  Type '(string | boolean)[]' is not assignable to type 'FooBase[]'.
    Type 'string | boolean' is not assignable to type 'FooBase'.
      Type 'true' is not assignable to type 'FooBase'.

7 foo1 = [...Array.isArray(foo2) ? foo2 : [foo2]];
  ~~~~

Playground Link: Link

Related Issues:

Activity

changed the title [-]Type of `false` unexpectedly loosens into type `boolean`[/-] [+]Type of `false` unexpectedly widens into type `boolean`[/+] on Jul 5, 2018
pedro-pedrosa

pedro-pedrosa commented on Jul 6, 2018

@pedro-pedrosa

Possibly related to this:

function trySomething(x: number) {
    if (x < 0) {
        return {
            result: false,
            message: 'Negative numbers not allowed'
        };
    }
    return {
        result: true,
    };
}

const a = trySomething(-1);
console.log(a.result ? 'Success' : `Error: ${a.message}`);

The return type of trySomething is { result: boolean; message: string; } | { result: boolean; message?: undefined; } while it should be { result: false; message: string; } | { result: true; message?: undefined; }

If I use strictNullChecks and try to use a.message I will get an error saying it's possibly undefined.

Playground link

Ghabriel

Ghabriel commented on Jul 6, 2018

@Ghabriel

@pedro-pedrosa I don't think it's related, TS always widens literals like that unless you do something like this, which works:

function trySomething(x: number) {
    if (x < 0) {
        return {
            result: false as false,
            message: 'Negative numbers not allowed'
        };
    }
    return {
        result: true as true,
    };
}

const a = trySomething(-1);
console.log(a.result ? 'Success' : `Error: ${a.message}`);
pedro-pedrosa

pedro-pedrosa commented on Jul 6, 2018

@pedro-pedrosa

Okay that probably makes some sense, typescript probably doesn't want to make the type too strict as users of the function may want to write values to the return value of the function.

cliffkoh

cliffkoh commented on Jul 6, 2018

@cliffkoh
Author

Note that there isn't a bug with string literal types, only false/true into boolean

type Foo = FooBase | FooArray;
type FooBase = 'blah' | 'mah';
type FooArray = FooBase[];

declare let foo1: Foo;
declare let foo2: Foo;
foo1 = [...Array.isArray(foo2) ? foo2 : [foo2]];

This works. type of [foo2] in the conditional does not get widened into string | string.

ahejlsberg

ahejlsberg commented on Jul 6, 2018

@ahejlsberg
Member

This is an issue resulting from two separate factors:

  • We have non-widening forms of the string and numeric literal types (Non-widening explicit literal types #11126), but the true and false literal types and enum literal types are always considered widening.
  • Spread expressions aren't considered to have a contextual type.

Because the array literal [foo2] occurs in a spread expression it isn't contextually typed by FooBase (the type of foo1) and therefore the string | false type is widened to string | boolean. This very similar assignment has no issues because the contextual type propagates appropriately:

declare let fa: Foo[];
declare let foo2: Foo;
fa = Array.isArray(foo2) ? foo2 : [foo2];

To fix this issue we need to propagate contextual types into spread expressions.

changed the title [-]Type of `false` unexpectedly widens into type `boolean`[/-] [+]Literal type `false` unexpectedly widens into type `boolean`[/+] on Jul 6, 2018
added this to the TypeScript 3.1 milestone on Jul 6, 2018
CS-Jfilmer

CS-Jfilmer commented on Mar 5, 2020

@CS-Jfilmer

Sorry, I feel like the answers I need are in this / #22596 but I can't grok it for the life of me

function x<Arg1, Arg2 extends (Arg1 extends true ? string[] : string)>(arg2IsArray: Arg1, arg2: Arg2){
}

x(true, 'string') // no error
x(true, ['string']) // no error

x(true as true, 'string') // error
x(true as true, ['string']) // no error

Is it that "true" means "boolean" if used in certain contexts to support

const x = true // x is a boolean not literally "true"?

But I just tried that and ts seems to think that x is just true not a boolean, idk. Anyway is there anyway to have the above example work without having to use true as true?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScript

Type

No type

Projects

No projects

Relationships

None yet

    Development

    Participants

    @Ghabriel@cliffkoh@pedro-pedrosa@weswigham@ahejlsberg

    Issue actions

      Literal type `false` unexpectedly widens into type `boolean` · Issue #25474 · microsoft/TypeScript