4.24 Type Guards

Type guards are particular expression patterns involving the ‘typeof’ and ‘instanceof’ operators that cause the types of variables or parameters to be narrowed to more specific types. For example, in the code below, knowledge of the static type of ‘x’ in combination with a ‘typeof’ check makes it safe to narrow the type of ‘x’ to string in the first branch of the ‘if’ statement and number in the second branch of the ‘if’ statement.

  1. function foo(x: number | string) {
  2. if (typeof x === "string") {
  3. return x.length; // x has type string here
  4. }
  5. else {
  6. return x + 1; // x has type number here
  7. }
  8. }

The type of a variable or parameter is narrowed in the following situations:

  • In the true branch statement of an ‘if’ statement, the type of a variable or parameter is narrowed by a type guard in the ‘if’ condition when true, provided no part of the ‘if’ statement contains assignments to the variable or parameter.
  • In the false branch statement of an ‘if’ statement, the type of a variable or parameter is narrowed by a type guard in the ‘if’ condition when false, provided no part of the ‘if’ statement contains assignments to the variable or parameter.
  • In the true expression of a conditional expression, the type of a variable or parameter is narrowed by a type guard in the condition when true, provided no part of the conditional expression contains assignments to the variable or parameter.
  • In the false expression of a conditional expression, the type of a variable or parameter is narrowed by a type guard in the condition when false, provided no part of the conditional expression contains assignments to the variable or parameter.
  • In the right operand of a && operation, the type of a variable or parameter is narrowed by a type guard in the left operand when true, provided neither operand contains assignments to the variable or parameter.
  • In the right operand of a || operation, the type of a variable or parameter is narrowed by a type guard in the left operand when false, provided neither operand contains assignments to the variable or parameter.

A type guard is simply an expression that follows a particular pattern. The process of narrowing the type of a variable x by a type guard when true or when false depends on the type guard as follows:

  • A type guard of the form x instanceof C, where x is not of type Any, C is of a subtype of the global type ‘Function’, and C has a property named ‘prototype’
    • when true, narrows the type of x to the type of the ‘prototype’ property in C provided it is a subtype of the type of x, or, if the type of x is a union type, removes from the type of x all constituent types that aren’t subtypes of the type of the ‘prototype’ property in C, or
    • when false, has no effect on the type of x.
  • A type guard of the form typeof x === s, where s is a string literal with the value ‘string’, ‘number’, or ‘boolean’,
    • when true, narrows the type of x to the given primitive type provided it is a subtype of the type of x, or, if the type of x is a union type, removes from the type of x all constituent types that aren’t subtypes of the given primitive type, or
    • when false, removes the primitive type from the type of x.
  • A type guard of the form typeof x === s, where s is a string literal with any value but ‘string’, ‘number’, or ‘boolean’,
    • when true, if x is a union type, removes from the type of x all constituent types that are subtypes of the string, number, or boolean primitive type, or
    • when false, has no effect on the type of x.
  • A type guard of the form typeof x !== s, where s is a string literal,
    • when true, narrows the type of x by typeof x === s when false, or
    • when false, narrows the type of x by typeof x === s when true.
  • A type guard of the form !expr
    • when true, narrows the type of x by expr when false, or
    • when false, narrows the type of x by expr when true.
  • A type guard of the form expr1 && expr2
    • when true, narrows the type of x by expr1 when true and then by expr2 when true, or
    • when false, narrows the type of x to T1 | T2, where T1 is the type of x narrowed by expr1 when false, and T2 is the type of x narrowed by expr1 when true and then by expr2 when false.
  • A type guard of the form expr1 || expr2
    • when true, narrows the type of x to T1 | T2, where T1 is the type of x narrowed by expr1 when true, and T2 is the type of x narrowed by expr1 when false and then by expr2 when true, or
    • when false, narrows the type of x by expr1 when false and then by expr2 when false.
  • A type guard of any other form has no effect on the type of x.

In the rules above, when a narrowing operation would remove all constituent types from a union type, the operation has no effect on the union type.

Note that type guards affect types of variables and parameters only and have no effect on members of objects such as properties. Also note that it is possible to defeat a type guard by calling a function that changes the type of the guarded variable.

TODO: Document user defined type guard functions.

In the example

  1. function isLongString(obj: any) {
  2. return typeof obj === "string" && obj.length > 100;
  3. }

the obj parameter has type string in the right operand of the && operator.

In the example

  1. function processValue(value: number | (() => number)) {
  2. var x = typeof value !== "number" ? value() : value;
  3. // Process number in x
  4. }

the value parameter has type () => number in the first conditional expression and type number in the second conditional expression, and the inferred type of x is number.

In the example

  1. function f(x: string | number | boolean) {
  2. if (typeof x === "string" || typeof x === "number") {
  3. var y = x; // Type of y is string | number
  4. }
  5. else {
  6. var z = x; // Type of z is boolean
  7. }
  8. }

the type of x is string | number | boolean in the left operand of the || operator, number | boolean in the right operand of the || operator, string | number in the first branch of the if statement, and boolean in the second branch of the if statement.

In the example

  1. class C {
  2. data: string | string[];
  3. getData() {
  4. var data = this.data;
  5. return typeof data === "string" ? data : data.join(" ");
  6. }
  7. }

the type of the data variable is string in the first conditional expression and string[] in the second conditional expression, and the inferred type of getData is string. Note that the data property must be copied to a local variable for the type guard to have an effect.

In the example

  1. class NamedItem {
  2. name: string;
  3. }
  4. function getName(obj: Object) {
  5. return obj instanceof NamedItem ? obj.name : "unknown";
  6. }

the type of obj is narrowed to NamedItem in the first conditional expression, and the inferred type of the getName function is string.