An assertion function (@returns {asserts value is string}) narrows its argument after the call, but only when TypeScript can see the assertion signature without inferring anything: every name in the call, check.assertString(…), must have a declared type. Otherwise the call is TS2775 and nothing is narrowed.
In JavaScript this comes up more often than in TypeScript, because objects, aliases and factory results are usually untyped: an object literal that collects helpers, const assert = assertString, or a function that returns one.
At a glance
| Code | TS2775 |
|---|---|
| Message | Assertions require every name in the call target to be declared with an explicit type annotation. |
| TypeScript's category | error |
| Listed under | Types do not match |
| Turned on by | No option: every checked file reports it |
| In .js files | Reported when the file is checked: checkJs in jsconfig.json, or // @ts-check at the top of the file |
The message is TypeScript's own text from its diagnostic messages file (Apache-2.0).
What it looks like
/**
* @param {unknown} value
* @returns {asserts value is string}
*/
function assertString(value) {
if (typeof value !== "string") throw new TypeError("Expected a string");
}
const check = { assertString };
/** @param {unknown} input */
export function shout(input) {
check.assertString(input);error TS2775: Assertions require every name in the call target to be declared with an explicit type annotation.
return String(input).toUpperCase();
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["**/*.js"]
} What causes it
- The assertion function is reached through a variable or object whose type is inferred (
const check = { assertString }). - An alias of the function (
const ensure = assertString) without a type.
How to fix it
Call the function by its declared name, or declare the type of what holds it
A function declaration has an explicit signature, so calling assertString(input) directly works. To keep the object, give it a type with @type; the fixed example uses typeof assertString for the property.
/**
* @param {unknown} value
* @returns {asserts value is string}
*/
function assertString(value) {
if (typeof value !== "string") throw new TypeError("Expected a string");
}
/** @type {{ assertString: typeof assertString }} */
const check = { assertString };
/** @param {unknown} input */
export function shout(input) {
assertString(input);
return input.toUpperCase();
}
/** @param {unknown} input */
export function whisper(input) {
check.assertString(input);
return input.toLowerCase();
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["**/*.js"]
} Gotchas
- A
constarrow function with its own JSDoc counts as declared, so/** @param {unknown} v @returns {asserts v is string} */ const assertString = (v) => …can be called directly. - After a TS2775 call the argument keeps its old type, so the next line often fails too (TS18046 for an
unknownvalue).
Sources
- TypeScript 3.7: assertion functions · TypeScript handbook
- diagnosticMessages.json (TypeScript 5.9.3) · TypeScript on GitHub
Last reviewed 21 October 2026.