Typescript type narrowed to never with instanceof in an if-else statement

Viewed 2444

I have a problem when I try to use instanceof with derived class instances in a if-else statement. Consider the following example:

interface IBaseModel {
    id: string
}

class BaseClass {
    model: IBaseModel
    constructor() {
    }

    setModel(model: IBaseModel) {
        this.model = model
    }

    getValueByName(name: string) {
        return this.model[name];
    }
}

interface IDerived1Model extends IBaseModel {
    height: number;
}

class Derived1 extends BaseClass {
    setModel(model: IDerived1Model) {
        super.setModel(model);
        // Do something with model...
    }
}

interface IDerived2Model extends IBaseModel {
    width: number;
}

class Derived2 extends BaseClass {
    setModel(model: IDerived2Model) {
        super.setModel(model);
        // Do something with model...
    }
}

const model1 = { id: "0", height: 42 };
const model2 = { id: "1", width: 24 };

const obj1 = new Derived1();
obj1.setModel(model1);

const obj2 = new Derived2();
obj2.setModel(model2);

const objs: BaseClass[] = [
    obj1,
    obj2
];

let variable: any = null;
for (const obj of objs) {
    if (obj instanceof Derived1) {
        variable = obj.getValueByName("height"); // Ok, obj is now of type `Derived1`
    } else if (obj instanceof Derived2) {
        variable = obj.getValueByName("width"); // Does not compile: Property 'getValueByName' does not exist on type 'never'
    }
    console.log("Value is: " + variable);
}

Here, getValueByName cannot be called on obj in the else part, as it was narrowed to never. Somehow, Typescript thinks that the else will never be executed, but it is wrong.

The important thing to look at is the overriding of the function setModel. The overrides have different parameter types, but those types inherit from the base IBaseModel type. If I change those to the base type, Typescript doesn't complain and compiles fine :

class Derived1 extends BaseClass {
    setModel(model: IBaseModel) {
        super.setModel(model);
        // Do something with model...
    }
}

class Derived2 extends BaseClass {
    setModel(model: IBaseModel) {
        super.setModel(model);
        // Do something with model...
    }
}

So my question is, why does having overrides with different types make the instanceof operator narrow the type of the object to never? Is this by design?

This was tested with Typescript 2.3.4, 2.4.1 and in the Typescript Playground.

Thanks!

1 Answers
Related