Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Union Type: One or the other failing. Not sure if this is by design or a bug

Tags:

typescript

Consider a situation where an object can have exactly the FooBar or Baz interface listed below.

interface FooBar {
    foo: string;
    bar: string;
}

interface Baz {
    baz: string;
}

It's my understanding that this "one or the other" situation can be handled by creating a union between FooBar and Baz.

type FooBarOrBaz = FooBar | Baz;

So far so good...

The issue I'm having is that the following object passes type checking:

const x: FooBarOrBaz = {
    foo: 'foo',
    baz: 'foo',
};

Is this a bug?

Playground Example

Thanks in advance!

like image 824
dsifford Avatar asked Oct 30 '22 11:10

dsifford


1 Answers

I think there is a bug here.

It can be made even more explicit by switching to classes and then using the type narrowing aspects of instanceof on union types.

Start with your code, but switched to classes:

class FooBar {
    foo: string;
    bar: string;
}

class Baz {
    baz: string;
}

type FooBarOrBaz = FooBar | Baz;

Create an instance, as you did - compiles fine.

const x: FooBarOrBaz = {
    foo: 'foo',
    baz: 'baz',
}

Check if we've got a FooBar:

if (x instanceof FooBar) {
    console.log("x is a FooBar : (" + x.foo + "," + x.bar + ")");
} else {
    console.log("x is not FooBar must be Baz : " + x.baz);
}

This compiles without warnings and prints x is not FooBar must be Baz : baz.

Check if we've got a Baz:

if (x instanceof Baz) {
    console.log("x is a Baz : (" + x.baz + ")");
} else {
    console.log("x is not Baz must be FooBar : " + x.foo + "," + x.bar);
}

Also compiles fine, but prints x is not Baz must be FooBar : foo,undefined

So either accepting this kind of input is a bug, or allowing it leads to a bug in the type narrowing of the instanceof type guard.

like image 167
Michael Anderson Avatar answered Jan 02 '23 21:01

Michael Anderson