Typescript: Support typeguards for class members

Created on 5 Aug 2018  路  5Comments  路  Source: microsoft/TypeScript

Search Terms

  • type guards
  • member type guards
  • class member type guard

Suggestion

Add an expression that makes type guard functions to check the class instance members instead of
the function's parameters, in place of value is X something like this.value is X.

Use Cases

This would be handy when checking if an instance field is of one type or another, in join types or
in optional types.

Currently this can be achieved with parameter checking, like:

class Foo {
  value: string | null;
  hasValue(value: string | null): value is string {
    return value !== null;
  }
}
const foo = new Foo();
if (foo.hasValue(foo.value)) {
  // Do something with foo that involves value not being null.
}

The problem of this approach is that if you want to maintain the member field _private_, you can't
take this approach, or this approach can only be used inside the class (Therefore hasValue should
be also private).

Examples

The latter can be solved telling Typescript to check the instance's member instead of the function
parameter:

class Foo {
  private value: string | null;
  public hasValue(): this.value is string {
    return this.value !== null;
  }
}
const foo = new Foo();
if (foo.hasValue()) {
  // Do something with foo that involves value not being null.
}

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. new expression-level syntax)
Question

Most helpful comment

You can already do this:

class Foo {
  public value: string | null;
  public hasValue(): this is { value: string } {
    return this.value !== null;
  }
}
const foo = new Foo();
foo.value.toString(); // Error, may be null
if (foo.hasValue()) {
  // OK
  foo.value.toString();
}

This doesn't actually work as expected if we use an else clause after the last if:

...
if (foo.hasValue()) {
  // OK
  foo.value.toString();
} else {
  (foo.value); // Should be null, but is actually string | null
}

In the last else clause, TypeScript cannot figure out that foo.value can only be null there. Any ways around this?

All 5 comments

You can already do this:

class Foo {
  public value: string | null;
  public hasValue(): this is { value: string } {
    return this.value !== null;
  }
}
const foo = new Foo();
foo.value.toString(); // Error, may be null
if (foo.hasValue()) {
  // OK
  foo.value.toString();
}

Yes, it can be used as a solution and works fine (Thanks for the answer).

However, the field cannot be private:

TS2546: Property 'value' has conflicting declarations and is inaccessible in type 'Optional & { value: any; }'.

(But with public field works, which is better than passing it again)

There's no point to provide a public type guard on a private member, though - if you're inside the class, you can already directly test against it; if you're outside the class, the fact that the private field isn't null doesn't change what you can do with the class instance.

So we can say that is a good pattern to check it internally using a static type guard, like:

class Foo<T> {
  private static check<T>(value: T | null): type is T {
    return value !== null;
  }
  private bar: T | null = null;
  public operation() {
    // or: if (this.bar !== null) {...}
    if (Foo.check<T>(this.bar)) {
      // Do things.
    }
  }
}

In which case, this can replace the explicit member guard.

The original intention was to provide a boolean-style method, like hasValue() that could let the compiler
know if what a getter returns is valid, but can be replaced with the other way round approach: Get the value, then checking if it was valid. I'll close this as it can be achieved with other approaches. Thanks for your time!

You can already do this:

class Foo {
  public value: string | null;
  public hasValue(): this is { value: string } {
    return this.value !== null;
  }
}
const foo = new Foo();
foo.value.toString(); // Error, may be null
if (foo.hasValue()) {
  // OK
  foo.value.toString();
}

This doesn't actually work as expected if we use an else clause after the last if:

...
if (foo.hasValue()) {
  // OK
  foo.value.toString();
} else {
  (foo.value); // Should be null, but is actually string | null
}

In the last else clause, TypeScript cannot figure out that foo.value can only be null there. Any ways around this?

Was this page helpful?
0 / 5 - 0 ratings