this is T (this-based guards)

Narrows the receiver itself by declaring a method whose return type is this is Type.

Signature
isFile(): this is FileRep;
isDirectory(): this is Directory;
isNetworked(): this is Networked & this;

Parameters

ParameterTypeDescription
$TypeTypeThe type the receiver narrows to. May be a subclass, an interface, or an intersection such as `Networked & this` that adds members while keeping the current type.

Return value

this is TypeA boolean at runtime. When true, the object the method was called on narrows to Type for the remainder of the branch.

Errors

Nothing at runtime. As with parameter predicates, tsc does not verify that the body agrees with the claim.

Changelog

  • PHP 3.0Works on getters as well as methods, so `get isFile(): this is FileRep` narrows on property access.
  • PHP 4.3Combines with #private fields for brand checks: `static isX(o: unknown): o is X { return #brand in o }` style guards became possible via `in` on private names.

this is Type is a type predicate whose subject is the receiver instead of a parameter. It is what lets an API expose node.isFile() rather than making callers import and apply a free function, and it is why Node's fs.Dirent and most virtual-filesystem libraries read the way they do.

When to reach for it

  • A base class with several subclasses, where callers hold the base type and need a readable way down.
  • Adding capability interfaces without a class hierarchy — isNetworked(): this is Networked & this keeps the current type and layers extra members on top.
  • Builder and fluent APIs that track completeness (hasBody(): this is Request & { body: string }).

What trips people up

It narrows the expression you called it on, and only if that expression is narrowable. if (fso.isFile()) narrows fso; if (getNode().isFile()) narrows nothing, because there is no reference to refine. Assign to a const first.

Like every predicate, the body is unchecked — returning a hard-coded true compiles. Back it with a real instanceof or a discriminant field.

Interfaces can declare these too; the implementing class must repeat the annotation, since a plain boolean return is not assignable to this is T.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Exposing isFile() and isDirectory() on a filesystem entry so callers reach content or children safely
  • Marking an optional capability such as isNetworked(): this is Networked & this
  • Letting a builder report that a required field is now set before allowing send()

Frequently asked questions

How is this is T different from a normal type predicate?

A normal predicate names a parameter and narrows the argument; `this is T` names no parameter and narrows the object the method was called on. Use it when the check naturally belongs to the class rather than to a free function.

Why doesn't the narrowing apply after I call the guard?

Either you called it on a temporary expression rather than a stable reference, or the reference is a `let` reassigned in between. Bind the object to a const and call the guard on that.

Can an interface declare a this-based guard?

Yes, and it is common for plugin contracts. The implementing class has to repeat the `this is T` return type, because a method typed to return plain boolean is not assignable to one returning a predicate.

More examples

Capability interfaces with Type & this

Prints "/local is local" then "/remote served from files.internal". The `& this` half is what keeps `n.path` reachable — narrowing to bare `Networked` would have replaced the type rather than extended it, and every other member would disappear inside the branch.

Example · typescript
interface Networked { host: string }

class Node {
  constructor(public path: string, private networked: boolean, public host = "") {}
  isNetworked(): this is Networked & this {
    return this.networked;
  }
}

const nodes = [new Node("/local", false), new Node("/remote", true, "files.internal")];

for (const n of nodes) {
  if (n.isNetworked()) {
    // n: Node & Networked — keeps path, gains host as a required string
    console.log(`${n.path} served from ${n.host}`);
  } else {
    console.log(`${n.path} is local`);
  }
}

A builder that tracks its own completeness

Prints "ready before from(): false" then SELECT * FROM users. `run` declares a `this` parameter requiring a non-optional table, so calling it without the isReady() guard is a compile error: "The 'this' context of type 'Query' is not assignable to method's 'this' of type 'Query & { table: string }'".

Example · typescript
class Query {
  private table?: string;
  from(t: string): this { this.table = t; return this; }
  isReady(): this is Query & { readonly table: string } {
    return this.table !== undefined;
  }
  run(this: Query & { table: string }): string {
    return `SELECT * FROM ${this.table}`;
  }
}

const q = new Query();
console.log("ready before from():", q.isReady());
q.from("users");
if (q.isReady()) console.log(q.run());

Discussion

  • Be the first to comment on this lesson.