Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[compiler] Don't outline functions that reference enclosing-function locals#36736

Open
poteto wants to merge 2 commits into
react:mainfrom
poteto:lauren/fix-34901-outline-captures
Open

[compiler] Don't outline functions that reference enclosing-function locals#36736
poteto wants to merge 2 commits into
react:mainfrom
poteto:lauren/fix-34901-outline-captures

Conversation

@poteto

@poteto poteto commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Function outlining hoisted closures to module scope when their only free variables looked like module-scope names. But in infer compilation mode, HIRBuilder classifies references to an enclosing non-compiled function's locals as ModuleLocal too: resolveIdentifier only distinguishes bindings inside the compiled function from bindings above it. A factory's component returning () => store outlined to function _temp() { return store; } at module level, throwing ReferenceError: store is not defined at runtime where the uncompiled source works.

The outlining candidate predicate now also requires that every ModuleLocal LoadGlobal (and StoreGlobal) name in the function, recursively through nested functions, resolves at the program scope. Names that resolve to a scope between module level and the compiled function block outlining; names that resolve to nothing (ambient globals, references to already-outlined siblings) remain outlineable. Re-resolution happens against the same scope HIRBuilder resolved against, so a factory local shadowing a module-level name is correctly rejected rather than silently rebinding.

The Rust port reaches the same decisions through its lowering: the branch that classifies these names already knows which case fired, so it records them into the environment and the outline pass consults that set. Mechanisms differ (TS re-resolves at outline time, Rust records at lowering time); decisions verified identical on both fixtures through the Rust e2e CLI.

Builds on the approach in #36539 by @tejasupmanyu, extended with nested-function recursion, StoreGlobal coverage, and binding-identity (rather than name-presence) scope checks; the positive control pins that module-const references still outline.

Verification: TS snap 1806/1806, Rust snap 1806/1806, cargo workspace green, scoped TS-vs-Rust HIR parity harness green; all pre-existing outlining fixtures byte-identical.

Closes #34901

@meta-cla meta-cla Bot added the CLA Signed label Jun 10, 2026
@poteto poteto marked this pull request as ready for review June 10, 2026 16:30
poteto and others added 2 commits June 17, 2026 01:01
…nction locals

In @compilationMode:"infer", a component defined inside a non-compiled
factory function can contain a callback whose only free variable is a
local of that factory. The callback's HIR context is empty (the factory
local is misclassified as ModuleLocal), so outlineFunctions hoists it to
module scope as `function _temp() { return store; }`, where `store` is
not in scope: the compiled fixture throws a ReferenceError at runtime
while the uncompiled version renders fine (react#34901). The
expect file documents today's wrong output; the fixture is added to
SproutTodoFilter until the fix lands.
…locals

When the compiled function is itself nested inside a non-compiled
function (e.g. a factory, with @compilationMode:"infer"), HIRBuilder
resolves free variables against parentFunction.scope.parent, which is
the enclosing function's scope rather than the program scope, so the
enclosing function's locals are classified as ModuleLocal and lowered
as LoadGlobal instead of being captured in the function's context. Such
functions looked outlineable (empty context), and outlining hoisted
them to module scope where the referenced name is not in scope,
throwing a ReferenceError at runtime (react#34901).

Fix in the outlining candidate check, in both compilers: reject a
candidate if it (or a nested function) references a name that does not
resolve to a module-scope binding. The TS pass re-resolves
LoadGlobal(ModuleLocal)/StoreGlobal names against the scope HIRBuilder
resolved against and requires the binding to be at program scope; the
Rust lowering records the misclassified names in
env.non_module_scope_names for outline_functions to consult, encoding
the same decision. Adopts the approach of community PR react#36539 with a
stricter predicate: binding-identity instead of name lookup (catching
shadowed module names), recursion into nested functions, StoreGlobal
coverage, and the Rust port.

Co-authored-by: tejasupmanyu <[email protected]>
@poteto poteto force-pushed the lauren/fix-34901-outline-captures branch from 9f250f9 to c871df1 Compare June 17, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Compiler Bug]: Incorrect closure hoisting causes runtime error

1 participant