I have a fairly complex use case in a project which uses vanilla JS and checks types using tsc
with type annotations written in JSDoc comments. I have a function which returns a function, and the returned function may recursively call itself, while also reassigning some closure variables.
Here is a silly example which gets the point across and will throw the same error:
/**
* @returns {function(): number}
*/
function circular() {
let num = Math.random();
return function tryAgain() {
if (num < 0.5) {
return num;
}
num = Math.random();
return tryAgain();
};
}
So, if I run a typecheck on this code using tsc
:
tsc --allowJs --checkJs --noEmit --strict --target ES2017 *.js
I get the following error:
error TS7023: 'tryAgain' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
To begin with, this error seems pretty erroneous. I clearly have an explicit return type annotation, which is the solution proposed for similar circular references using proper TypeScript rather than JSDoc.
But I'd be happy with any workaround that will get this to compile without a massive refactor that removes closures and/or recursion. I have already tried a number of possible workarounds, none of which have worked.
Wrapping the return function with an annotation:
return /** @type {function(): number} */ (function tryAgain() {
. . .
});
Wrapping the recursive call with an annotation:
return /** @type {number} */ (tryAgain());
Wrapping the recursive call with coercion:
return Number(tryAgain());
At this point I'm totally stumped on how to annotate this properly, or at the very least how to get the darn thing to compile.
Separating the function definition from the return
lets you annotate tryAgain
, which gets rid of the error:
/**
* @returns {function(): number}
*/
function circular() {
let num = Math.random();
/**
* @returns {number}
*/
function tryAgain() {
if (num < 0.5) {
return num;
}
num = Math.random();
return tryAgain();
}
return tryAgain;
}
Doing it inline also works:
/**
* @returns {function(): number}
*/
function circular() {
let num = Math.random();
return /** @returns {number} */ function tryAgain() {
if (num < 0.5) {
return num;
}
num = Math.random();
return tryAgain();
};
};