Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Interfaces in Union Types Cause Errors with Instanceof

I've googled for the last hour and I haven't found a good answer or explanation for my problem.

I have a member variable defined as a union type, of a primitive(number) or an interface (KnockoutObservable), and I can not use the instanceof or typeof typeguards without generating an error. I am using VS2013 update 4 with Typescript 1.4. I have set up a few examples to demonstrate the issue:

class foo {
    foo() {}
}

class bar {
    bar() {}
}

interface baz {
    baz();
}

// This case breaks
    var var1: number|foo;

    if (typeof var1 === "number") {
        var1 = 5;
    }
    // Generates error "The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter."
    else if (var1 instanceof foo) {
        var1.foo();
    }

    // This also breaks, same error as above
    if (var1 instanceof number) {
        var1 = 5;
    }
    else if (var1 instanceof foo) {
        var1.foo();
    }

    // This case works
    var var2: foo|bar;

    if (var2 instanceof foo) {
        var2.foo();
    }
    else if (var2 instanceof bar) {
        var2.bar();
    }

    // This case breaks as well
    var var3: foo|baz;

    if (var3 instanceof foo) {
        var3.foo();
    }
    // Generates error: "Cannot find name 'baz'."
    else if (var3 instanceof baz) {
        var3.baz();
    }

My question is why do cases 1 and 3 break? We are creating KnockoutJS components, where the parameter could be an observable or a primitive. Since KnockoutObservable is an interface, this pretty much shuts down being able to use union types in our pattern; if we want the parameter to be either we have to revert to using 'any'.

Some of the things I have found about this (such as here) seem to imply this is fixed in 1.5. Can anybody give me the lowdown on this?

like image 673
Hubuki Kai Avatar asked Apr 16 '15 18:04

Hubuki Kai


1 Answers

Note that assigning to a variable anywhere in a function body "turns off" type guards on that variable, so I've removed assignments from this example.

Basically, there's the case that works as expected, a case that ought to work but doesn't, and a case that doesn't work because there's no runtime type information for interfaces. instanceof is a JavaScript operator that checks the prototype chain of an object, not a TypeScript construct for doing type operations.

    var var1: number|foo;
    // OK
    if (typeof var1 === "number") { }

    // Bug #2775
    // https://github.com/Microsoft/TypeScript/issues/2775
    if (var1 instanceof foo) { }
    if (var1 instanceof number) { }

    // OK
    var var2: foo|bar;
    if (var2 instanceof foo) { }
    if (var2 instanceof bar) { }

    // TypeScript does not have reflection; there is no
    // value 'baz' to 'instanceof' at runtime.
    if (var3 instanceof baz) {
        var3.baz();
    }

The error about x instanceof number is also intentional; there is no runtime value number. You might be tempted to write x instanceof Number; this would be a mistake (42 instanceof Number is false, not true).

like image 113
Ryan Cavanaugh Avatar answered Oct 23 '22 18:10

Ryan Cavanaugh