Typescript: Incorrect assignment to variable of union type

Created on 18 Jul 2018  路  5Comments  路  Source: microsoft/TypeScript

TypeScript Version: 2.9.2, 3.0.0-rc, 3.1.0-dev.20180717

Search Terms: union types

Code

interface DisabledInterface {
    enabled: false;
}

interface EnabledInterfaceOne {
    enabled: true;
    type: 'fixed';
}

interface EnabledInterfaceTwo {
    enabled: true;
    type: 'percent';
}

type CommonType = DisabledInterface | EnabledInterfaceOne | EnabledInterfaceTwo;

const a: CommonType = { enabled: false, type: 'fixed' };

Expected behavior:

Error on assignment to a const.

Actual behavior:

No errors.

Playground Link: link

Question

Most helpful comment

@RikkiGibson That is related to #20863. I believe the issue is that:

1) Excess property checking is not enabled for untagged unions.
2) Excess property checking intermediate results are not used in later assignability checks. In your example excess property checking alone is insufficient because there are no excess properties for the entire union. TypeScript would need to know that Foo1 has excess properties so the only candidate for assignability is Foo2, but prop3 is missing.

All 5 comments

The interesting part is when we modify the DisabledInterface to the following:

interface DisabledInterface {
    enabled: false;
    type: undefined;
}

the compilation fails as expected. Maybe it is related to #10586 in some way?

I think this is to do with excess property checking on unions which only works for tagged unions. In your example the field enabled is not a discriminant for the union (because EnabledInterfaceOne and EnabledInterfaceTwo share the property). As there is no discriminant, excess property checking gets disabled. The follow works:

interface DisabledInterface {
    enabled: false;
}

interface EnabledInterfaceOne {
    enabled: true;
    type: 'fixed';
}

type CommonType = DisabledInterface | EnabledInterfaceOne

const a: CommonType = { enabled: false, type: 'fixed' }; // error

because enabled is now a correct discriminant so excess property checking gets turned on.

The reason @Nipheris's example works is because it's an ill-typed property, not an excess one.

I believe I've found another instance of the same behavior in a relatively simple case:

interface Foo1 {
    prop1: string;
}

interface Foo2 {
    prop1: string;
    prop2: string;
    prop3: string;
}

type Foo = Foo1 | Foo2;

// expected an error but no error was reported
const foo: Foo = {
    prop1: "something",
    prop2: "hello"
}

I would expect TypeScript to either require that I specify prop1 or specify all of prop1, prop2, and prop3. Is this by design?

@RikkiGibson That is related to #20863. I believe the issue is that:

1) Excess property checking is not enabled for untagged unions.
2) Excess property checking intermediate results are not used in later assignability checks. In your example excess property checking alone is insufficient because there are no excess properties for the entire union. TypeScript would need to know that Foo1 has excess properties so the only candidate for assignability is Foo2, but prop3 is missing.

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

uber5001 picture uber5001  路  3Comments

blendsdk picture blendsdk  路  3Comments

dlaberge picture dlaberge  路  3Comments

manekinekko picture manekinekko  路  3Comments

siddjain picture siddjain  路  3Comments