Skip to content

[compiler] Improved ref validation for non-mutating functions#608

Closed
everettbu wants to merge 1 commit into
mainfrom
pr35893
Closed

[compiler] Improved ref validation for non-mutating functions#608
everettbu wants to merge 1 commit into
mainfrom
pr35893

Conversation

@everettbu

Copy link
Copy Markdown

Mirror of facebook/react#35893
Original author: josephsavona


If a function is known to freeze its inputs, and captures refs, then we can safely assume those refs are not mutated during render.

An example is React Native's PanResponder, which is designed for use in interaction handling. Calling PanResponder.create() creates an object that shouldn't be interacted with at render time, so we can treat it as freezing its arguments, returning a frozen value, and not accessing any refs in the callbacks passed to it. ValidateNoRefAccessInRender is updated accordingly - if we see a Freeze and ImmutableCapture for the same place in the same instruction, we know that it's not being mutated.

Note that this is a pretty targeted fix. One weakness is that we may not always emit a Freeze effect if a value is already frozen, which could cause this optimization not to kick in. The worst case there is that you'd just get a ref access in render error though, not miscompilation. And we could always choose to always emit Freeze effects, even for frozen values, just to retain the information for validations like this.

@everettbu everettbu added CLA Signed React Core Team Opened by a member of the React Core Team labels Feb 24, 2026
@greptile-apps

greptile-apps Bot commented Feb 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR improves ValidateNoRefAccessInRender to leverage per-instruction aliasing effects when available, enabling more precise ref validation for functions with known signatures. The key mechanism is a new else if branch that iterates over instr.effects for non-hook function calls, applying different validation levels (validateNoDirectRefValueAccess vs validateNoRefPassedToFunction) depending on the effect kind. For ImmutableCapture, it checks whether a co-occurring Freeze effect exists on the same operand to distinguish known-safe signatures (like PanResponder.create) from unknown functions that happen to receive frozen values.

  • Restructured the CallExpression/MethodCall validation into four distinct branches: ref-lvalue/hook calls, JSX-interpolated calls, effects-aware calls (new), and the fallback for unknown functions
  • Added PanResponder.create type signature to the shared-runtime type provider with Freeze + ImmutableCapture aliasing effects, modeling the React Native API pattern where callbacks are captured but not invoked during render
  • Updated the error.validate-mutate-ref-arg-in-render test from console.log to mutate to ensure the test exercises the unknown-function fallback path (since console.log now has a known signature with effects)
  • Added new panresponder-ref-in-callback fixture demonstrating that refs captured in PanResponder.create callbacks no longer produce false positive errors
  • Minor documentation additions to CLAUDE.md for linting and formatting commands

Confidence Score: 4/5

  • This PR is safe to merge — the validation changes are conservative (worst case is a false positive error, not miscompilation) and all effect kinds are exhaustively handled.
  • The switch statement in the new effects-based branch exhaustively covers all 16 AliasingEffect kinds. The ImmutableCapture+Freeze heuristic is sound per the PR description's analysis: false negatives (missing a Freeze effect) only produce unnecessary errors, not silent miscompilation. The test coverage is appropriate with both a positive test (PanResponder fixture) and updated negative test (mutate-ref-arg). The only minor concern is the O(n²) pattern from the nested .some() call inside the effects loop, but this is negligible for real-world effect list sizes.
  • Pay close attention to compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts — the core validation logic change.

Important Files Changed

Filename Overview
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts Core change: restructured ref validation for function calls to use per-effect validation when aliasing effects are available. Adds effects-based switch over all 16 AliasingEffect kinds with appropriate validation per kind. The ImmutableCapture+Freeze co-occurrence heuristic is the key mechanism enabling the PanResponder optimization.
compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts Adds PanResponder.create type signature with Freeze + ImmutableCapture aliasing effects to model the React Native PanResponder API for testing.
compiler/packages/snap/src/sprout/shared-runtime.ts Adds PanResponder runtime stub (simple identity function) for fixture evaluation.
compiler/packages/babel-plugin-react-compiler/src/tests/fixtures/compiler/panresponder-ref-in-callback.js New test fixture demonstrating that PanResponder.create with ref callbacks compiles without errors.
compiler/packages/babel-plugin-react-compiler/src/tests/fixtures/compiler/error.validate-mutate-ref-arg-in-render.js Updated test: changed from console.log (known global with signature) to mutate (unknown function) to exercise the else branch of the new validation logic.
compiler/CLAUDE.md Documentation-only: added linting and formatting command instructions.

Last reviewed commit: 5570223

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

If a function is known to freeze its inputs, and captures refs, then we can safely assume those refs are not mutated during render.

An example is React Native's PanResponder, which is designed for use in interaction handling. Calling `PanResponder.create()` creates an object that shouldn't be interacted with at render time, so we can treat it as freezing its arguments, returning a frozen value, and not accessing any refs in the callbacks passed to it. ValidateNoRefAccessInRender is updated accordingly - if we see a Freeze <place> and ImmutableCapture <place> for the same place in the same instruction, we know that it's not being mutated.

Note that this is a pretty targeted fix. One weakness is that we may not always emit a Freeze effect if a value is already frozen, which could cause this optimization not to kick in. The worst case there is that you'd just get a ref access in render error though, not miscompilation. And we could always choose to always emit Freeze effects, even for frozen values, just to retain the information for validations like this.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@everettbu

Copy link
Copy Markdown
Author

Upstream PR was closed or merged. Code is synced via branch mirror.

@everettbu everettbu closed this Feb 24, 2026
@everettbu
everettbu deleted the pr35893 branch February 24, 2026 21:22
@everettbu
everettbu restored the pr35893 branch February 24, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants