5

Consider the following example:

class A {}
class B extends A {
    foo() {
        return 6;
    }
}

const variable: A = new B();

const isValid = variable instanceof B;

if (isValid) {
    variable.foo();
}

The .foo() call prompts the following error:

enter image description here

It makes sense because variable is of type A. But variable.foo() will only run if variable is an instance of B.

The issue does not happen when doing it like this:

class A {}
class B extends A {
    foo() {
        return 6;
    }
}

const variable: A = new B();

if (variable instanceof B) {
    variable.foo();
}

Why does it matter if the condition is stored in a variable, rather than written explicitly within the if?

1
  • 3
    instanceof acts as an implicit type guard, but the compiler does not carry this information over through variables. variable is simply a boolean which doesn't have the type narrowing property anymore. Commented Nov 28, 2020 at 14:23

1 Answer 1

2

Update for TS4.4:

TypeScript 4.4 will introduce support for control flow analysis of aliased conditions. This means you can sometimes save the results of type guards into consts and use them later. Your code above will therefore work as desired with no modifications.

Playground link to code


Pre TS4.4 answer:

There is a suggestion at microsoft/TypeScript#12184 (among others, see that issue for links to them) to allow storing the results of a type guard into a boolean variable for later use. While most people agree it would be nice to have this, the lead architect for the language said:

This would require us to track what effects a particular value for one variable implies for other variables, which would add a good deal of complexity (and associated performance penalty) to the control flow analyzer. Still, we'll keep it as a suggestion.


To expand on that a little bit, currently the compiler recognizes that, inside a code block where variable instanceof B has been tested and evaluated to true, the type of variable can be narrowed to B... but outside of that scope, the compiler can just "forget" about such narrowing:

if (variable instanceof B) {
    variable.foo(); // okay
}
variable.foo(); // error

In order for something like the following to work:

const isValid = variable instanceof B;

if (isValid) {
  variable.foo(); 
}

the compiler would not be allowed to "forget" the narrowing until isValid itself... or anything that depends on isValid goes out of scope. This might seem reasonable on the surface, but for every case like this where you want such memory, it seems like there would be many cases where the extra work would be unnecessary. For example, if you had this:

const bar = Math.random() < 0.5 ? { a: 123, b: 456 } : undefined;
const baz = bar?.a;

Should the compiler "remember" that baz will be a number if and only if bar is an {a: number, b: number}? Perhaps... but unless sometime later someone actually uses this fact, as in:

const qux = typeof baz === "number" ? bar.b : 789;

then keeping track of that is just wasted effort.


It's possible that in the future someone will devise a way to do this that's better than the current situation without making control flow analysis prohibitively expensive. Maybe someone who wants such behavior on a boolean variable could manually annotate it with something like a type predicate, as mentioned in this comment on a related issue?

const isValid: variable is B = variable instanceof B; // not valid syntax now

But for now, it's not part of the language. If you really feel strongly about this you might want to go to the relevant GitHub issues and give them your 👍 or describe your use case and explain why the current behavior of using non-stored type guards is insufficient. If enough people do this it raises the chances that something will eventually be implemented. I wouldn't count on it, though.


For the foreseeable future, you will be better off just using your type guards immediately instead of trying to save them for later. One way to get closer to this "delayed" behavior is to refactor so that what you are saving is more functional than a boolean:

const varIfValid = variable instanceof B ? variable : undefined;

if (varIfValid) {
  varIfValid.foo(); // okay
}

That works because varIfValid is B | undefined, a union more directly related to calling foo() than true | false is.


Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.