Skip to content

allow storing results of narrowing of class properties in booleans for further narrowing #53267

Closed

Description

@SteveRusin

Bug Report

🔎 Search Terms

https://www.google.com/search?q=typescript+does+not+narrow+type+of+stored+variable&oq=typescript+does+not+narrow+type+of+stored+variable&aqs=chrome..69i57j69i64.8615j0j7&sourceid=chrome&ie=UTF-8

🕗 Version & Regression Information

  • This is the behavior in every version I tried

⏯ Playground Link

Playground link with relevant code

💻 Code

class Utils {
  static isDefined<T>(value: T): value is NonNullable<T> {
    return value != null;
  };
}

class A {
  public testNumber: number | undefined;

  constructor() {
    const isNumber = Utils.isDefined(this.testNumber);

    if (isNumber) {
      // Type 'number | undefined' is not assignable to type 'number'
      const x: number = this.testNumber;
    }
  }
}

function foo(x: number | undefined) {
  const isNum = Utils.isDefined(x);

  if(isNum) {
    // Works. No error here
    const y: number = x;
  }
}

🙁 Actual behavior

The narrowed type of class property is not inferred if narrowing result stored in variable

I expect the type to be correctly inferred as it's working for regular variables inside a function
This PR fixed this bug for functions

Activity

  1. fatcerberus commented on Mar 15, 2023

    @fatcerberus

    This seems intentional and is not specific to classes:

    Narrowing through indirect references occurs only when the conditional expression or discriminant property access is declared in a const variable declaration with no type annotation, and the reference being narrowed is a const variable, a readonly property, or a parameter for which there are no assignments in the function body.

    testNumber is not readonly.

  2. MartinJohns commented on Mar 15, 2023

    @MartinJohns
    Contributor

    Bruce Pascoe (@fatcerberus) But even if you make it readonly it's not working.

  3. RyanCavanaugh commented on Mar 17, 2023

    @RyanCavanaugh
    Member

    It does need to be readonly. Looks like we have a bug here when the property is on this specifically, since this code works as expected:

    class Utils {
      static isDefined<T>(value: T): value is NonNullable<T> {
        return value != null;
      };
    }
    
    class A {
      public readonly testNumber: number | undefined;
    
      foo() {
        const obj: this = this;
        const isNumber = Utils.isDefined(obj.testNumber);
    
        if (isNumber) {
          // ok
          const x: number = obj.testNumber;
        }
      }
    }

    Common source of bug here when we check for something being an Identifier, which this isn't. From a type perspective these examples are the same so it's probably a simple fix.

  4. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions