fix(transformer): hoist lowered async declarations (#22770)

Fixes semantic scope mismatches when async function declarations are lowered inside sloppy block scopes.

Before this change, `async_to_generator` kept the original async function binding, and the generated helper binding, in the current block scope. After transform those declarations are no longer async:

```js
with (obj) {
  async function ref() {}
}
```

becomes roughly:

```js
with (obj) {
  function ref() {
    return _ref.apply(this, arguments);
  }
  function _ref() {
    _ref = babelHelpers.asyncToGenerator(function* () {});
    return _ref.apply(this, arguments);
  }
}
```

A fresh semantic rebuild treats those lowered plain function declarations with Annex B hoisting rules, so in sloppy block scopes their bindings can belong to the enclosing var scope instead of the block scope. The transformed semantic tree was still using the pre-lowered block placement, which caused rebuild mismatches in Test262 coverage.

This PR makes `async_to_generator` choose the same binding scope that a rebuilt semantic tree would use for the emitted plain function declarations, while preserving the existing behavior for TypeScript, strict mode, var scopes, and cases where the hoist scope already has a binding.
This commit is contained in:
camc314 committed 2026-07-02 19:47:32 +00:00
1 parent 3a7a606544
commit 018c0e57ec
2 files changed
+35 -138

No files matched your search

@@ -451,9 +451,17 @@ impl<'a> AsyncGeneratorExecutor<'a> {
Self::create_placeholder_params(&wrapper_function.params, wrapper_scope_id, ctx);
let params = mem::replace(&mut wrapper_function.params, params);
let function_binding_scope_id =
Self::function_declaration_binding_scope_id(wrapper_function.id.as_ref(), ctx);
if function_binding_scope_id != ctx.current_scope_id()
&& let Some(id) = &wrapper_function.id
{
Self::move_binding_identifier_to_target_scope(function_binding_scope_id, id, ctx);
}
let bound_ident = Self::create_bound_identifier(
wrapper_function.id.as_ref(),
ctx.current_scope_id(),
Self::function_declaration_binding_scope_id(None, ctx),
SymbolFlags::Function,
ctx,
);
@@ -510,6 +518,31 @@ impl<'a> AsyncGeneratorExecutor<'a> {
}
}
/// Returns the binding scope a rebuilt semantic tree would use for a plain function
/// declaration emitted at the current position.
fn function_declaration_binding_scope_id(
id: Option<&BindingIdentifier<'a>>,
ctx: &TraverseCtx<'a>,
) -> ScopeId {
if ctx.state.source_type.is_typescript() {
return ctx.current_scope_id();
}
let scope_flags = ctx.current_scope_flags();
if scope_flags.is_var() || scope_flags.is_strict_mode() {
return ctx.current_scope_id();
}
let hoist_scope_id = ctx.current_hoist_scope_id();
if let Some(id) = id
&& ctx.scoping().scope_has_binding(hoist_scope_id, id.name)
{
ctx.current_scope_id()
} else {
hoist_scope_id
}
}
/// Transforms async arrow functions into generator functions wrapped in the asyncToGenerator helper.
pub(self) fn transform_arrow_function(
&self,
+1 -137
View File
@@ -2,7 +2,7 @@ commit: de8e621c
semantic_test262 Summary:
AST Parsed : 47240/47240 (100.00%)
Positive Passed: 47020/47240 (99.53%)
Positive Passed: 47028/47240 (99.55%)
semantic Error: tasks/coverage/test262/test/language/eval-code/direct/async-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js
Scope flags mismatch:
after transform: ScopeId(4): ScopeFlags(Function)
@@ -760,67 +760,11 @@ Scope flags mismatch:
after transform: ScopeId(6): ScopeFlags(Function)
rebuilt : ScopeId(2): ScopeFlags(Function | DirectEval)
semantic Error: tasks/coverage/test262/test/language/statements/async-function/unscopables-with-in-nested-fn.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncToGenerator", "callCount", "count", "v"]
rebuilt : ScopeId(0): ["_asyncToGenerator", "_ref", "callCount", "count", "ref", "v"]
Bindings mismatch:
after transform: ScopeId(1): ["_ref", "ref"]
rebuilt : ScopeId(1): []
Symbol scope ID mismatch for "ref":
after transform: SymbolId(3): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
Symbol scope ID mismatch for "_ref":
after transform: SymbolId(7): ScopeId(1)
rebuilt : SymbolId(6): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/async-function/unscopables-with.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncToGenerator", "callCount", "count", "v"]
rebuilt : ScopeId(0): ["_asyncToGenerator", "_ref", "callCount", "count", "ref", "v"]
Bindings mismatch:
after transform: ScopeId(1): ["_ref", "ref"]
rebuilt : ScopeId(1): []
Symbol scope ID mismatch for "ref":
after transform: SymbolId(3): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
Symbol scope ID mismatch for "_ref":
after transform: SymbolId(7): ScopeId(1)
rebuilt : SymbolId(6): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/async-generator/eval-var-scope-syntax-err.js
Scope flags mismatch:
after transform: ScopeId(4): ScopeFlags(Function)
rebuilt : ScopeId(2): ScopeFlags(Function | DirectEval)
semantic Error: tasks/coverage/test262/test/language/statements/async-generator/unscopables-with-in-nested-fn.js
Bindings mismatch:
after transform: ScopeId(0): ["_wrapAsyncGenerator", "callCount", "count", "v"]
rebuilt : ScopeId(0): ["_ref", "_wrapAsyncGenerator", "callCount", "count", "ref", "v"]
Bindings mismatch:
after transform: ScopeId(1): ["_ref", "ref"]
rebuilt : ScopeId(1): []
Symbol scope ID mismatch for "ref":
after transform: SymbolId(3): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
Symbol scope ID mismatch for "_ref":
after transform: SymbolId(7): ScopeId(1)
rebuilt : SymbolId(6): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/async-generator/unscopables-with.js
Bindings mismatch:
after transform: ScopeId(0): ["_wrapAsyncGenerator", "callCount", "count", "v"]
rebuilt : ScopeId(0): ["_ref", "_wrapAsyncGenerator", "callCount", "count", "ref", "v"]
Bindings mismatch:
after transform: ScopeId(1): ["_ref", "ref"]
rebuilt : ScopeId(1): []
Symbol scope ID mismatch for "ref":
after transform: SymbolId(3): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
Symbol scope ID mismatch for "_ref":
after transform: SymbolId(7): ScopeId(1)
rebuilt : SymbolId(6): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-arrow-function-expression.js
Scope flags mismatch:
after transform: ScopeId(2): ScopeFlags(StrictMode | Function | Arrow)
@@ -1209,34 +1153,6 @@ Scope flags mismatch:
after transform: ScopeId(1): ScopeFlags(StrictMode | DirectEval)
rebuilt : ScopeId(1): ScopeFlags(StrictMode)
semantic Error: tasks/coverage/test262/test/language/statements/for-await-of/async-func-decl-dstr-obj-id-put-unresolvable-no-strict.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncIterator", "_asyncToGenerator"]
rebuilt : ScopeId(0): ["_asyncIterator", "_asyncToGenerator", "_fn", "fn"]
Bindings mismatch:
after transform: ScopeId(1): ["_fn", "fn", "iterCount", "promise"]
rebuilt : ScopeId(1): ["iterCount", "promise"]
Symbol scope ID mismatch for "fn":
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(3): ScopeId(0)
Symbol scope ID mismatch for "_fn":
after transform: SymbolId(10): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/for-await-of/async-func-decl-dstr-obj-prop-put-unresolvable-no-strict.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncIterator", "_asyncToGenerator"]
rebuilt : ScopeId(0): ["_asyncIterator", "_asyncToGenerator", "_fn", "fn"]
Bindings mismatch:
after transform: ScopeId(1): ["_fn", "fn", "iterCount", "promise"]
rebuilt : ScopeId(1): ["iterCount", "promise"]
Symbol scope ID mismatch for "fn":
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(3): ScopeId(0)
Symbol scope ID mismatch for "_fn":
after transform: SymbolId(10): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/for-await-of/async-func-decl-dstr-obj-rest-getter.js
Scope parent mismatch:
after transform: ScopeId(3): Some(ScopeId(2))
@@ -1257,34 +1173,6 @@ Scope parent mismatch:
after transform: ScopeId(3): Some(ScopeId(2))
rebuilt : ScopeId(6): Some(ScopeId(5))
semantic Error: tasks/coverage/test262/test/language/statements/for-await-of/async-gen-decl-dstr-obj-id-put-unresolvable-no-strict.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncIterator", "_awaitAsyncGenerator", "_wrapAsyncGenerator"]
rebuilt : ScopeId(0): ["_asyncIterator", "_awaitAsyncGenerator", "_fn", "_wrapAsyncGenerator", "fn"]
Bindings mismatch:
after transform: ScopeId(1): ["_fn", "fn", "iterCount", "promise"]
rebuilt : ScopeId(1): ["iterCount", "promise"]
Symbol scope ID mismatch for "fn":
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
Symbol scope ID mismatch for "_fn":
after transform: SymbolId(11): ScopeId(1)
rebuilt : SymbolId(5): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/for-await-of/async-gen-decl-dstr-obj-prop-put-unresolvable-no-strict.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncIterator", "_awaitAsyncGenerator", "_wrapAsyncGenerator"]
rebuilt : ScopeId(0): ["_asyncIterator", "_awaitAsyncGenerator", "_fn", "_wrapAsyncGenerator", "fn"]
Bindings mismatch:
after transform: ScopeId(1): ["_fn", "fn", "iterCount", "promise"]
rebuilt : ScopeId(1): ["iterCount", "promise"]
Symbol scope ID mismatch for "fn":
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(4): ScopeId(0)
Symbol scope ID mismatch for "_fn":
after transform: SymbolId(11): ScopeId(1)
rebuilt : SymbolId(5): ScopeId(0)
semantic Error: tasks/coverage/test262/test/language/statements/for-await-of/async-gen-decl-dstr-obj-rest-getter.js
Scope parent mismatch:
after transform: ScopeId(3): Some(ScopeId(2))
@@ -1328,21 +1216,9 @@ after transform: SymbolId(1) "x"
rebuilt : SymbolId(1) "x"
semantic Error: tasks/coverage/test262/test/language/statements/switch/scope-lex-async-function.js
Bindings mismatch:
after transform: ScopeId(0): ["_asyncToGenerator"]
rebuilt : ScopeId(0): ["_asyncToGenerator", "_x", "x"]
Bindings mismatch:
after transform: ScopeId(1): ["_x", "x"]
rebuilt : ScopeId(1): []
Symbol scope ID mismatch for "x":
after transform: SymbolId(0): ScopeId(1)
rebuilt : SymbolId(1): ScopeId(0)
Symbol reference IDs mismatch for "x":
after transform: SymbolId(0): []
rebuilt : SymbolId(1): [ReferenceId(7)]
Symbol scope ID mismatch for "_x":
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(2): ScopeId(0)
Reference symbol mismatch for "x":
after transform: <None>
rebuilt : SymbolId(1) "x"
@@ -1351,21 +1227,9 @@ after transform: ["arguments", "require", "x"]
rebuilt : ["arguments", "require"]
semantic Error: tasks/coverage/test262/test/language/statements/switch/scope-lex-async-generator.js
Bindings mismatch:
after transform: ScopeId(0): ["_wrapAsyncGenerator"]
rebuilt : ScopeId(0): ["_wrapAsyncGenerator", "_x", "x"]
Bindings mismatch:
after transform: ScopeId(1): ["_x", "x"]
rebuilt : ScopeId(1): []
Symbol scope ID mismatch for "x":
after transform: SymbolId(0): ScopeId(1)
rebuilt : SymbolId(1): ScopeId(0)
Symbol reference IDs mismatch for "x":
after transform: SymbolId(0): []
rebuilt : SymbolId(1): [ReferenceId(7)]
Symbol scope ID mismatch for "_x":
after transform: SymbolId(1): ScopeId(1)
rebuilt : SymbolId(2): ScopeId(0)
Reference symbol mismatch for "x":
after transform: <None>
rebuilt : SymbolId(1) "x"