asserts (assertion functions)

Narrows a value for the rest of the scope by throwing when a runtime check fails.

Signature
function assert(condition: any, msg?: string): asserts condition;
function assertIsString(val: any): asserts val is string;
  • Added in TypeScript 3.7

Parameters

ParameterTypeDescription
$conditionanyForm one: the parameter whose truthiness is asserted. Everything the caller's argument expression narrowed is applied at the call site.
$valanyForm two: the parameter named on the left of `is`, narrowed to the type on the right. Must be a parameter of this function.
$msgoptionalstring= undefinedOptional extra parameters are ordinary parameters and play no part in the assertion.

Return value

asserts condition | asserts val is TypeNothing (void at runtime). If the call returns rather than throwing, the compiler treats the condition as true, or the named parameter as the asserted type, for the remainder of the enclosing scope.

Errors

Whatever your body throws. tsc errors with "Assertions require every name in the call target to be declared with an explicit type annotation" if the callee is not a function declaration or an explicitly annotated const.

Changelog

  • PHP 3.7Assertion signatures introduced in both the `asserts condition` and `asserts val is Type` forms.
  • PHP 3.7The call target must be a named function declaration or a const with an explicit type annotation; an arrow assigned to an inferred const is rejected at every call site.

An assertion function narrows by not returning. If control reaches the line after the call, the compiler accepts the assertion as fact for the rest of the scope — no if block, no indentation.

The two forms

  • asserts condition — the parameter's truthiness is the claim. Whatever the caller's argument expression would have narrowed inside an if is applied after the call, so assert(typeof x === "string") makes x a string.
  • asserts val is Type — the named parameter becomes Type. This is the form to reach for when the check itself lives inside the helper.

What trips people up

The declaration rules are strict and the error message is confusing. The callee must be a function declaration, or a const whose type is written out in full:

const assertIsString: (v: unknown) => asserts v is string = (v) => { /* ... */ };

An arrow assigned to a bare const, a method reached through an object, or an imported binding without an annotation all fail with "Assertions require every name in the call target to be declared with an explicit type annotation."

Also remember the assertion is erased. If your body forgets to throw, the compiler still believes you.

Example

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

When to use it

  • Turning an optional config field into a required one at the top of a function
  • Replacing a chain of non-null assertions with one checked invariant
  • Guarding a value parsed from JSON before handing it to typed code

Frequently asked questions

Why do I get "Assertions require every name in the call target to be declared with an explicit type annotation"?

Because the function you are calling is an arrow or expression assigned to a const without a written-out type. Change it to a `function` declaration, or annotate the const as `const f: (v: unknown) => asserts v is T = ...`.

What is the difference between asserts x is T and x is T?

`x is T` returns a boolean you branch on; `asserts x is T` returns nothing and throws, narrowing everything after the call in the same scope. Use the assertion when a failing check means the program should stop.

Does an assertion function do anything at runtime on its own?

No. The `asserts` clause is erased with the rest of the types, so your body must actually throw. A function that only logs a warning will leave the compiler confidently wrong.

More examples

The condition form, and the annotation rule

Prints HELLO. `assert(typeof str === "string")` narrows `str` for the rest of `yell` even though there is no if statement. The commented-out arrow is the shape that produces "Assertions require every name in the call target to be declared with an explicit type annotation".

Example · typescript
function assert(condition: unknown, msg?: string): asserts condition {
  if (!condition) throw new Error(msg ?? "Assertion failed");
}

function yell(str: unknown): string {
  assert(typeof str === "string", "expected a string");
  return str.toUpperCase(); // str: string
}

// Legal: const with the type written out in full.
const assertPositive: (n: number) => asserts n is number =
  (n) => { if (n <= 0) throw new RangeError(`${n} <= 0`); };

// Illegal at every call site — inferred const:
// const bad = (n: unknown): asserts n is number => { ... };

console.log(yell("hello"));

Assertion versus predicate on the same check

Prints predicate: /hello then assertion: /hello. Same runtime check, two ergonomics: the predicate gives you a branch you can handle, the assertion gives you flat code that crashes on the unhappy path. Choose the assertion only where a failure genuinely is a bug.

Example · typescript
type Draft = { status: "draft" };
type Published = { status: "published"; url: string };
type Post = Draft | Published;

function isPublished(p: Post): p is Published {
  return p.status === "published";
}

function assertPublished(p: Post): asserts p is Published {
  if (p.status !== "published") throw new Error("post is not published");
}

const post: Post = { status: "published", url: "/hello" };

if (isPublished(post)) console.log("predicate:", post.url);

assertPublished(post);
console.log("assertion:", post.url); // no if needed

Discussion

  • Be the first to comment on this lesson.