Skip to content

Invalid code generated for async functions containing multiple for..of loops with same variable #2910

Closed

Description

I ran into a bizarre error after having rewritten some code to use Promises, which I'll include here for the benefit of anyone who may be searching for it:

TypeError: Cannot read property 'done' of undefined
    at $$jscomp$generator$Engine_$.program_ (/Users/ash/src/furcng/tmp.js:502:37)
    at $$jscomp$generator$Engine_$$nextStep_$ [as nextStep_] (/Users/ash/src/furcng/tmp.js:456:38)
    at $$jscomp$generator$Engine_$$next_$ [as next_] (/Users/ash/src/furcng/tmp.js:417:15)
    at $$jscomp$generator$Generator_$.$this$next$ [as next] (/Users/ash/src/furcng/tmp.js:477:22)
    at $passValueToGenerator$$ (/Users/ash/src/furcng/tmp.js:277:25)

If an async function contains multiple for...of loops sharing the same variable name, some of which contain await and some of which do not contain await, Closure Compiler will generate invalid code for accessing the variable in the latter.

Here is a minimal test case which reproduces the issue.

async function breakIt() {
	for (const number of [1,2,3]) {
		await Promise.resolve(true)
	}

	for (const number of [1,2,3]) {
	}
}

breakIt().then(() => console.log('done'))

The generated code (with options --formatting=PRETTY_PRINT --debug=true) is as follows:

function breakIt() {
  return $jscomp.asyncExecutePromiseGeneratorFunction(function $jscomp$generator$function() {
    var $$jscomp$iter$0$$;
    return $jscomp.generator.createGenerator($jscomp$generator$function, function($$jscomp$generator$context$$) {
      switch($$jscomp$generator$context$$.nextAddress) {
        case 1:
          $$jscomp$iter$0$$ = $jscomp.makeIterator([1, 2, 3]), $$jscomp$key$number$$ = $$jscomp$iter$0$$.next();
        case 2:
          if ($$jscomp$key$number$$.done) {
            $$jscomp$generator$context$$.jumpTo(4);
            break;
          }
          return $$jscomp$generator$context$$.yield(Promise.resolve(!0), 3);
        case 3:
          $$jscomp$iter$0$$.next();
          $$jscomp$generator$context$$.jumpTo(2);
          break;
        case 4:
          for (var $$jscomp$iter$1$$ = $jscomp.makeIterator([1, 2, 3]), $$jscomp$key$number$$ = $$jscomp$iter$1$$.next(); !$$jscomp$key$number$$.done; $$jscomp$key$number$$ = $$jscomp$iter$1$$.next()) {
          }
          $$jscomp$generator$context$$.jumpToEnd();
      }
    });
  });
}

Note that the number variable has been renamed to $$jscomp$key$number$$ for both loops. However, the variable is declared as part of the second loop (without await), which breaks the first loop as its value is cleared between generator calls. If the second loop is removed, $$jscomp$key$number$$ is declared outside the generator, and the first loop functions correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions