asserts (assertion functions)
Narrows a value for the rest of the scope by throwing when a runtime check fails.
function assert(condition: any, msg?: string): asserts condition;
function assertIsString(val: any): asserts val is string;- Added in TypeScript 3.7
Parameters
| Parameter | Type | Description |
|---|---|---|
$condition | any | Form one: the parameter whose truthiness is asserted. Everything the caller's argument expression narrowed is applied at the call site. |
$val | any | Form two: the parameter named on the left of `is`, narrowed to the type on the right. Must be a parameter of this function. |
$msgoptional | string= undefined | Optional 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 anifis applied after the call, soassert(typeof x === "string")makesxa string.asserts val is Type— the named parameter becomesType. 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
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".
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.
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