Typescript: TypeGuarding not applied with explicit instanceof comparison to true/false

Created on 12 Aug 2019  Β·  8Comments  Β·  Source: microsoft/TypeScript

From https://github.com/microsoft/vscode/issues/77921


TypeScript Version: 3.6.0-dev.20190810


Search Terms:

  • type guard
  • instanceof

Code

function foo(target: Number | String) {
    if (target instanceof String === true) {
        target.toLowerCase();
    }
}

Expected behavior:
No compile error.

Type of target in the conditional statement should be String

Actual behavior:
Error: Property 'toLowerCase' does not exist on type 'String | Number'. Property 'toLowerCase' does not exist on type 'Number'.

The guarding works properly if you omit the explicit === true:

function foo(target: Number | String) {
    if (target instanceof String) {
        target.toLowerCase();
    }
}

Playground Link:

Related Issues:

Won't Fix

Most helpful comment

For future reference, here's a better example:

declare const isNonNullable: <T>(t: T) => t is NonNullable<T>;

declare const t: string | undefined;

if (!isNonNullable(t)) {
} else {
    t.toUpperCase(); // βœ… no error, good
}

if (isNonNullable(t) === false) {
} else {
    t.toUpperCase(); // ❌ unexpected error
}

if (isNonNullable(t)) {
    t.toUpperCase(); // βœ… no error, good
}

if (isNonNullable(t) === true) {
    t.toUpperCase(); // ❌ unexpected error
}

All 8 comments

Narrowing forms have to be identified syntactically, so we pay a perf penalty for every possible form that's checked for. Since there's no real reason to write it this way, we don't consider === true forms

I feel like the code example in my original ticket was completely reasonable and the current behavior very counter-intuitive. I understand that changing the behavior is undesireable if it implies a performance penalty, but it would really help me out if someone could point out my mistake or what I should be doing differently to work around the issue.

Thanks in advance

@oliversalzburg You can write:

if( !(target instanceof HTMLElement) ) {
    return;
}

Narrowing forms have to be identified syntactically, so we pay a perf penalty for every possible form that's checked for. Since there's no real reason to write it this way, we don't consider === true forms

if( !(target instanceof HTMLElement) ) {
return;
}

@ahejlsberg said the similar thing when he implemented this type guard.

What about cases like this one?

function foo(target: Number | String) {
    const targetIsString = target instanceof String;
    if (targetIsString) {
        target.toLowerCase();
    }
}

We are currently writing TS definitions for parts of our code base and it's not uncommon that a parameter to a JS function can be of several types, especially if some arguments are optional. This causes us to write code that checks the type of the argument and branches accordingly.

This often leads to TS reporting errors as discussed in this issue, even though the code is perfectly valid. This is confusing developers and forces them to write code to adapt to limitations of TS, which is unfortunate.

Regardless, thanks for the explanations. The cleanest solution appears to be to write multiple functions that are stricter in their expected argument types.

@oliversalzburg That's a different thing - TS doesn't track dependencies between variables like that, it can only narrow variables directly included in the conditional expression. There are many, many tickets about that exact pattern; but it's actually a very big can of worms to make it work in general ("What does the (narrowed) type of this variable imply for all the other ones in scope?"), so it's largely considered a design limitation that that doesn't work.

@RyanCavanaugh

Since there's no real reason to write it this way,

I think have a good reason for writing x === false instead of !x: I find the logical NOT operator hurts readabilityβ€”I've lost count of how many times I've missed a ! when skimming through code. For this reason I would like to see narrowing work equally when using both forms.

Related: https://github.com/microsoft/TypeScript/issues/32956

For future reference, here's a better example:

declare const isNonNullable: <T>(t: T) => t is NonNullable<T>;

declare const t: string | undefined;

if (!isNonNullable(t)) {
} else {
    t.toUpperCase(); // βœ… no error, good
}

if (isNonNullable(t) === false) {
} else {
    t.toUpperCase(); // ❌ unexpected error
}

if (isNonNullable(t)) {
    t.toUpperCase(); // βœ… no error, good
}

if (isNonNullable(t) === true) {
    t.toUpperCase(); // ❌ unexpected error
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

zhuravlikjb picture zhuravlikjb  Β·  3Comments

kyasbal-1994 picture kyasbal-1994  Β·  3Comments

bgrieder picture bgrieder  Β·  3Comments

CyrusNajmabadi picture CyrusNajmabadi  Β·  3Comments

dlaberge picture dlaberge  Β·  3Comments