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
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.
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
Foo1has excess properties so the only candidate for assignability isFoo2, butprop3is missing.