Repository: facebook/react
Pull request: [compiler][ez] Only fail gating hoisting check for referenced identifiers


Reduce false positive bailouts by using the same `isReferencedIdentifier` logic that the compiler also uses for determining context variables and a function's own hoisted declarations.

Details:
Previously, we counted every babel identifier as a reference. This is
problematic because babel counts most string symbols as an identifier.

```js
print(x);  // x is an identifier as expected
obj.x      // x is.. also an identifier here
{x: 2}     // x is also an identifier here
```

This PR adds a check for `isReferencedIdentifier`. Note that only non-lval
references pass this check. This should be fine as we don't need to hoist function declarations before writes to the same lvalue (which should error in strict mode anyways)
```js
print(x);  // isReferencedIdentifier(x) -> true
obj.x      // isReferencedIdentifier(x) -> false
{x: 2}     // isReferencedIdentifier(x) -> false
x = 2      // isReferencedIdentifier(x) -> false
```
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32596).
* __->__ #32596
* #32595
* #32594
* #32593
* #32522
* #32521

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
patch: @@ -1143,7 +1143,7 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
        * A null scope means there's no function scope, which means we're at the
        * top level scope.
        */
-      if (scope === null) {
+      if (scope === null && id.isReferencedIdentifier()) {
         errors.pushErrorDetail(
           new CompilerErrorDetail({
             reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`,

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.expect.md
patch: @@ -0,0 +1,60 @@
+
+## Input
+
+```javascript
+// @gating
+import {identity, useHook as useRenamed} from 'shared-runtime';
+const _ = {
+  useHook: () => {},
+};
+identity(_.useHook);
+
+function useHook() {
+  useRenamed();
+  return <div>hello world!</div>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: useHook,
+  params: [{}],
+};
+
+```
+
+## Code
+
+```javascript
+import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
+import { c as _c } from "react/compiler-runtime"; // @gating
+import { identity, useHook as useRenamed } from "shared-runtime";
+const _ = {
+  useHook: isForgetEnabled_Fixtures() ? () => {} : () => {},
+};
+identity(_.useHook);
+const useHook = isForgetEnabled_Fixtures()
+  ? function useHook() {
+      const $ = _c(1);
+      useRenamed();
+      let t0;
+      if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+        t0 = <div>hello world!</div>;
+        $[0] = t0;
+      } else {
+        t0 = $[0];
+      }
+      return t0;
+    }
+  : function useHook() {
+      useRenamed();
+      return <div>hello world!</div>;
+    };
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: useHook,
+  params: [{}],
+};
+
+```
+      
+### Eval output
+(kind: ok) <div>hello world!</div>
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-nonreferenced-identifier-collision.js
patch: @@ -0,0 +1,16 @@
+// @gating
+import {identity, useHook as useRenamed} from 'shared-runtime';
+const _ = {
+  useHook: () => {},
+};
+identity(_.useHook);
+
+function useHook() {
+  useRenamed();
+  return <div>hello world!</div>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: useHook,
+  params: [{}],
+};