TypeScript compiler equality narrowing is incorrect after non-local mutation

Viewed 30

The TypeScript compiler can perform equality narrowing to infer that a value has a specific literal type. However, that inference may last longer than is actually valid. In the example below, the compiler thinks that this.state must have the literal type "ALIVE" longer than it actually does.

class GameData {
    state: string = "ALIVE";

    maybeUpdateState() {
        if (Math.random() < 0.5) {
            this.state = "DEAD";
        }
    }

    doTick() {
        if (this.state == "ALIVE") {
            console.log("Game is alive");
            this.maybeUpdateState();
            if (this.state == "DEAD") { // <-- Compiler error here!
                console.log("Game died");
            }
        }
    }
}

new GameData().doTick();

This results in the following (incorrect) compiler error:

This condition will always return 'false' since the types '"ALIVE"' and '"DEAD"' have no overlap.

(Playground link)

This is obviously not true. I've figured out that there are ways I could work around this at the line where I do the second equality test -- for instance, I could rewrite it as if (this.state as string == "DEAD").

But instead of potentially having to do that every place I want to do an equality test against that property, is there any way that I can update maybeUpdateState() to let the compiler know that it mutates the object such that inferences about the object's properties may no longer be valid after it is called?

I haven't been able to find any other discussion about this specific issue in my online searching, so references to other Q&As/issues/posts would also be appreciated.

1 Answers

This should fix it:

if (this.state.includes("DEAD")) {}

Now, if you wanna make this mutation a bit more predictable use a type:

type GameState = "ALIVE" | "DEAD";

state: GameState = "ALIVE";

So even if you fudge up the codes and typo this.state = "DEAAD", for example, the compiler then flunks the build which is desirable.

Related