Search code examples
typescripttypeguards

Why write the return type of a type guard as `this is (…) & this` in TypeScript?


I'm reading the Classes section of the handbook, but there is a difference that makes me confused.

In the following examples:

  1. In Example 1, they first explicitly wrote the return type this is Networked & this. I can understand this part.

  2. Then, in Example 2, they only did this is { value: T }, but the type of box in the if-block is Box<string> & { value: string; }.

I don't know why the second case works. Based on the first example, I expect that it should be this is { value: T } & this instead. Does it mean I can always omit the explicit & this?


Example 1

class FileSystemObject {
  isFile(): this is FileRep {
    return this instanceof FileRep;
  }
  isDirectory(): this is Directory {
    return this instanceof Directory;
  }
  isNetworked(): this is Networked & this { // the return type here.
    return this.networked;
  }
  constructor(public path: string, private networked: boolean) {}
}
 
class FileRep extends FileSystemObject {
  constructor(path: string, public content: string) {
    super(path, false);
  }
}
 
class Directory extends FileSystemObject {
  children: FileSystemObject[];
}
 
interface Networked { // this interface that matters.
  host: string;
}
 
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
  fso.content;
} else if (fso.isDirectory()) {
  fso.children;
} else if (fso.isNetworked()) {
  fso.host; // const fso: Networked & FileSystemObject
}

Example 2

class Box<T> {
  value?: T;
 
  hasValue(): this is { value: T } { // here, there is no `& this`.
    return this.value !== undefined;
  }
}
 
const box = new Box<string>();
box.value = "Gameboy";
 
if (box.hasValue()) {
  box.value; // notice the type of box here.
}


Solution

  • Tried it just in case: yes, you can omit this. The type guard is always performed on the this instance, the narrowed version is always some variant of this, typescript is smart enough to do that.

    Works the same as Extract

    https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union