Skip to content

[compiler] Don't emit spurious import { c as _c } for discarded functions#36500

Merged
poteto merged 1 commit into
facebook:mainfrom
poteto:react-compiler-no-spurious-memo-cache-import
May 21, 2026
Merged

[compiler] Don't emit spurious import { c as _c } for discarded functions#36500
poteto merged 1 commit into
facebook:mainfrom
poteto:react-compiler-no-spurious-memo-cache-import

Conversation

@poteto
Copy link
Copy Markdown
Collaborator

@poteto poteto commented May 20, 2026

What

Codegen registers _c (the memo cache import) as a side effect whenever a function compiles with memo slots. The registration persists on ProgramContext.imports even if the function is later discarded ('use no forget', 'use no memo', lint mode, validation errors). If other applied functions in the file compile to 0 memo slots, the stale import { c as _c } from "react/compiler-runtime"; leaks into the output.

Fix

In applyCompiledFunctions, drop the memo cache import if no applied function uses memo slots. If react/compiler-runtime has no remaining specifiers, drop the module entry too so we don't emit a bare import "react/compiler-runtime";.

Reproducer

use-no-forget-multiple-with-eslint-suppression.js:

import {useRef} from 'react';

const useControllableState = options => {};
function NoopComponent() {}

function Component() {
  'use no forget';
  const ref = useRef(null);
  // eslint-disable-next-line react-hooks/rules-of-hooks
  ref.current = 'bad';
  return <button ref={ref} />;
}

NoopComponent applies with 0 memo slots. Component is opted out, but codegen already registered _c for it. Before:

import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";

After:

import { useRef } from "react";

Prior art

TS counterpart to the Rust port's fix in 7e26eb8. That commit also added no-cache-slots-no-import.js, which codifies the "no memo slots, no import" rule.

Test plan

  • yarn snap: 1719/1719 passing
  • Snapshot for use-no-forget-multiple-with-eslint-suppression loses its spurious _c import
  • no-cache-slots-no-import still passes

…ctions

Codegen calls `programContext.addMemoCacheImport()` as a side effect
whenever a function compiles with memo slots. The registration sticks
even if the function is later discarded (`'use no forget'`, lint-only
mode, validation errors), so other applied functions with 0 memo slots
end up emitting a `_c` import that's never referenced.

In `applyCompiledFunctions`, drop the import if no applied function
actually uses memo slots. If `react/compiler-runtime` then has no
specifiers left, drop the module entry too so we don't emit a bare
`import "react/compiler-runtime";`.

TS counterpart to the Rust port's fix in 7e26eb8.

Test plan:
- `yarn snap`: 1719/1719 passing
- Snapshot for `use-no-forget-multiple-with-eslint-suppression` loses
  its spurious `_c` import; no other fixtures changed.
- `no-cache-slots-no-import` (the Rust-port precedent fixture) still
  passes.
@meta-cla meta-cla Bot added the CLA Signed label May 20, 2026
@github-actions github-actions Bot added the React Core Team Opened by a member of the React Core Team label May 20, 2026
@poteto poteto requested review from mofeiZ and mvitousek May 21, 2026 00:11
@poteto
Copy link
Copy Markdown
Collaborator Author

poteto commented May 21, 2026

@mofeiZ @mvitousek going to merge this one since it's just cleaning up unused import

@poteto poteto merged commit 008a6d4 into facebook:main May 21, 2026
24 checks passed
@poteto poteto deleted the react-compiler-no-spurious-memo-cache-import branch May 21, 2026 04:57
github-actions Bot pushed a commit that referenced this pull request May 21, 2026
…ctions (#36500)

## What

Codegen registers `_c` (the memo cache import) as a side effect whenever
a function compiles with memo slots. The registration persists on
`ProgramContext.imports` even if the function is later discarded (`'use
no forget'`, `'use no memo'`, lint mode, validation errors). If other
applied functions in the file compile to 0 memo slots, the stale `import
{ c as _c } from "react/compiler-runtime";` leaks into the output.

## Fix

In `applyCompiledFunctions`, drop the memo cache import if no applied
function uses memo slots. If `react/compiler-runtime` has no remaining
specifiers, drop the module entry too so we don't emit a bare `import
"react/compiler-runtime";`.

## Reproducer

`use-no-forget-multiple-with-eslint-suppression.js`:

```js
import {useRef} from 'react';

const useControllableState = options => {};
function NoopComponent() {}

function Component() {
  'use no forget';
  const ref = useRef(null);
  // eslint-disable-next-line react-hooks/rules-of-hooks
  ref.current = 'bad';
  return <button ref={ref} />;
}
```

`NoopComponent` applies with 0 memo slots. `Component` is opted out, but
codegen already registered `_c` for it. Before:

```js
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
```

After:

```js
import { useRef } from "react";
```

## Prior art

TS counterpart to the Rust port's fix in
7e26eb8. That commit also added
`no-cache-slots-no-import.js`, which codifies the "no memo slots, no
import" rule.

## Test plan

- `yarn snap`: 1719/1719 passing
- Snapshot for `use-no-forget-multiple-with-eslint-suppression` loses
its spurious `_c` import
- `no-cache-slots-no-import` still passes

DiffTrain build for [008a6d4](008a6d4)
github-actions Bot pushed a commit that referenced this pull request May 21, 2026
…ctions (#36500)

## What

Codegen registers `_c` (the memo cache import) as a side effect whenever
a function compiles with memo slots. The registration persists on
`ProgramContext.imports` even if the function is later discarded (`'use
no forget'`, `'use no memo'`, lint mode, validation errors). If other
applied functions in the file compile to 0 memo slots, the stale `import
{ c as _c } from "react/compiler-runtime";` leaks into the output.

## Fix

In `applyCompiledFunctions`, drop the memo cache import if no applied
function uses memo slots. If `react/compiler-runtime` has no remaining
specifiers, drop the module entry too so we don't emit a bare `import
"react/compiler-runtime";`.

## Reproducer

`use-no-forget-multiple-with-eslint-suppression.js`:

```js
import {useRef} from 'react';

const useControllableState = options => {};
function NoopComponent() {}

function Component() {
  'use no forget';
  const ref = useRef(null);
  // eslint-disable-next-line react-hooks/rules-of-hooks
  ref.current = 'bad';
  return <button ref={ref} />;
}
```

`NoopComponent` applies with 0 memo slots. `Component` is opted out, but
codegen already registered `_c` for it. Before:

```js
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
```

After:

```js
import { useRef } from "react";
```

## Prior art

TS counterpart to the Rust port's fix in
7e26eb8. That commit also added
`no-cache-slots-no-import.js`, which codifies the "no memo slots, no
import" rule.

## Test plan

- `yarn snap`: 1719/1719 passing
- Snapshot for `use-no-forget-multiple-with-eslint-suppression` loses
its spurious `_c` import
- `no-cache-slots-no-import` still passes

DiffTrain build for [008a6d4](008a6d4)
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.

1 participant