[compiler] Do not inline IIFEs in value blocks (#33548)

As discussed in chat, this is a simple fix to stop introducing labels
inside expressions.

The useMemo-with-optional test was added in
d70b2c2c4e
and crashes for the same reason- an unexpected label as a value block
terminal.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33548).
* __->__ #33548
* #33546
This commit is contained in:
Jordan Brown 2025-06-16 21:53:50 -04:00 committed by GitHub
parent 75e78d243f
commit 90bee81902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 212 additions and 131 deletions

View File

@ -17,6 +17,7 @@ import {
InstructionKind,
LabelTerminal,
Place,
isStatementBlockKind,
makeInstructionId,
promoteTemporary,
reversePostorderBlocks,
@ -90,6 +91,11 @@ export function inlineImmediatelyInvokedFunctionExpressions(
*/
const queue = Array.from(fn.body.blocks.values());
queue: for (const block of queue) {
/*
* We can't handle labels inside expressions yet, so we don't inline IIFEs if they are in an
* expression block.
*/
if (isStatementBlockKind(block.kind)) {
for (let ii = 0; ii < block.instructions.length; ii++) {
const instr = block.instructions[ii]!;
switch (instr.value.kind) {
@ -189,6 +195,7 @@ export function inlineImmediatelyInvokedFunctionExpressions(
}
}
}
}
if (inlinedFunctions.size !== 0) {
// Remove instructions that define lambdas which we inlined

View File

@ -1,32 +0,0 @@
## Input
```javascript
function Component(props) {
return (
useMemo(() => {
return [props.value];
}) || []
);
}
```
## Error
```
1 | function Component(props) {
2 | return (
> 3 | useMemo(() => {
| ^^^^^^^^^^^^^^^
> 4 | return [props.value];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 5 | }) || []
| ^^^^^^^^^^^^^ Todo: Support labeled statements combined with value blocks (conditional, logical, optional chaining, etc) (3:5)
6 | );
7 | }
8 |
```

View File

@ -1,7 +0,0 @@
function Component(props) {
return (
useMemo(() => {
return [props.value];
}) || []
);
}

View File

@ -0,0 +1,40 @@
## Input
```javascript
function Component(props) {
const x = props.foo
? 1
: (() => {
throw new Error('Did not receive 1');
})();
return items;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: true}],
};
```
## Code
```javascript
function Component(props) {
props.foo ? 1 : _temp();
return items;
}
function _temp() {
throw new Error("Did not receive 1");
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: true }],
};
```
### Eval output
(kind: exception) items is not defined

View File

@ -0,0 +1,13 @@
function Component(props) {
const x = props.foo
? 1
: (() => {
throw new Error('Did not receive 1');
})();
return items;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: true}],
};

View File

@ -0,0 +1,47 @@
## Input
```javascript
import {useMemo} from 'react';
function Component(props) {
return (
useMemo(() => {
return [props.value];
}) || []
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 1}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
function Component(props) {
const $ = _c(2);
let t0;
if ($[0] !== props.value) {
t0 = (() => [props.value])() || [];
$[0] = props.value;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 1 }],
};
```
### Eval output
(kind: ok) [1]

View File

@ -0,0 +1,13 @@
import {useMemo} from 'react';
function Component(props) {
return (
useMemo(() => {
return [props.value];
}) || []
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 1}],
};