Grathau
Grathau

Reputation: 41

Typescript: How to add type guards for enums in union types?

The following code gives errors:

  1. The left-hand side of an 'instanceof' expression must be of type 'any', an object or a type parameter.
  2. Type 'E | C' is not assignable to type 'E'. Type 'C' is not assignable to type 'E'.
    enum E { FIRST = 1, SECOND = 2 }; 

    class C {
        value: E;    

        constructor(arg: C | E) {
            if (arg instanceof C) { // 1.
                this.value = arg.value;
            } else {
               this.value = arg; // 2.
            }
        }
    }

    var a: C = new C(E.SECOND);
    console.log('a.value = ' + a.value);

    var b: C = new C(a);
    console.log('b.value = ' + b.value);

Despite the errors the code seems to compile fine on TypeScript Playground and does the expected.

Upvotes: 4

Views: 3635

Answers (2)

Fenton
Fenton

Reputation: 250902

Ryan is, of course, both eloquent and accurate, but I can offer you a temporary workaround that you may find useful. Despite its possible use, please don't accept this as the answer as long-term the resolution of the issues related to type guards will be better.

class C {
    value: E;    

    constructor(arg: C | number) {
        if(typeof arg === 'number') {
            this.value = arg;
        } else {
            this.value = arg.value;
        }
    }
}

This rather unsavoury solution leans on the fact that enums and numbers happily play together. You also benefit from the side-effect of typeof checks narrowing the else statement.

Upvotes: 3

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 220974

Two issues.

There's a bug in the compiler that instanceof isn't allowed on union types when one of its constituents isn't an object type. This is being fixed (https://github.com/Microsoft/TypeScript/issues/2775)

The other thing is that instanceof does not cause narrowing in the else block (https://github.com/Microsoft/TypeScript/issues/1719) because failing an instanceof check doesn't necessarily mean the object doesn't match the specified interface. This is currently "by design" but I'd encourage you to leave a comment that you find this behavior surprising or undesirable (I certainly do).

Upvotes: 6

Related Questions