Skip to content

Return type annotations ignored with recursive closures using JSDoc #31836

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
delventhalz opened this issue Jun 9, 2019 · 5 comments
Open
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@delventhalz
Copy link

delventhalz commented Jun 9, 2019

TypeScript Version: 3.5.1

Search Terms:

  • "implicit any type"
  • "circular reference"
  • "jsdoc"
  • "recursive"
  • "closure"

Code

I have a fairly complex use case in a project which uses vanilla JS with type annotations in JSDoc comments. The long and the short is that there is a function which returns a function, which may recursively call itself and will reassign some closure variables.

Here is a silly example which gets the point across and demonstrates the same issue:

/**
 * @returns {function(): number}
 */
function circular() {
  let rand = Math.random();

  return /** @type {function(): number} */ (function tryAgain() {
    if (rand < 0.5) {
      return rand;
    }

    rand = Math.random();
    return tryAgain();
  });
}

Expected behavior:

TypeScript should know that the return type of tryAgain is a number.

Actual behavior:

When run with:

tsc --allowJs --checkJs --noEmit --strict --target ES2017 *.js

The following error is thrown:

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.

At the very least, this error seems pretty erroneous. The tryAgain function has two return type annotations (I have tried both styles in an attempt to fix this). The larger issue is that I need some way to get TypeScript to compile this code without a massive refactor.

Playground Link:

None (code is JavaScript).

Related Issues:

This has a circular reference similar to #26623. However for that issue the solution was to add an explicit return type annotation. In my case (perhaps because I am using JSDoc), TypeScript seems to be ignoring all explicit annotations.

@delventhalz
Copy link
Author

A helpful fellow on StackOverflow suggested using a @returns annotation instead of @type, which works! It is unclear to me why, or whether this issue should still be kept open as a bug, but if anyone else runs into this issue, this code will not throw any errors:

/**
 * @returns {function(): number}
 */
function circular() {
  let rand = Math.random();

  return /** @returns {number} */ function tryAgain() {
    if (rand < 0.5) {
      return rand;
    }

    rand = Math.random();
    return tryAgain();
  };
}

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jun 13, 2019
@ma2ciek
Copy link

ma2ciek commented Nov 18, 2019

The same problem occurs with generator functions when fnA depends on fnB and fnB depends on fnA.

@boazblake
Copy link

A helpful fellow on StackOverflow suggested using a @returns annotation instead of @type, which works! It is unclear to me why, or whether this issue should still be kept open as a bug, but if anyone else runs into this issue, this code will not throw any errors:

/**
 * @returns {function(): number}
 */
function circular() {
  let rand = Math.random();

  return /** @returns {number} */ function tryAgain() {
    if (rand < 0.5) {
      return rand;
    }

    rand = Math.random();
    return tryAgain();
  };
}

fails here

@delventhalz
Copy link
Author

@boazblake You introduced a bug into the link you posted. You added a line break after the return statement:

  return /** @returns {number} */
   function tryAgain() {
    if (rand < 0.5) {

You cannot have a return statement on its own line. Automatic Semicolon Insertion will insert a semicolon directly after the return, causing you to return undefined. You can plainly see this if you look at the generated code in the right column:

    return; /** @returns {number} */
    function tryAgain() {
        if (rand < 0.5) {

To fix your issue, delete the extraneous line break. The workaround then works as advertised:

  return /** @returns {number} */ function tryAgain() {
    if (rand < 0.5) {

@boazblake
Copy link

@delventhalz Thank you 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants