@samitouri / QOS-React / commits / ea0c17b095

[compiler] loosen computed key restriction for compiler (#34902)

We have a whole ton of compiler errors due to us using a helper to return breakpoints for CSS-in-js, which results in code like: ``` const styles = { [responsive.up('xl')]: { ... } } ``` this results in TONS of bailouts due to `(BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression`. I was looking into what it would take to fix it and why we don't allow it, and following the paper trail is seems like the gotchas have been fixed with the new mutability aliasing model that is fully rolled out. It looks like this is the same pattern/issue that was fixed (see https://github.com/facebook/react/blob/main/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.js and the old bug in https://github.com/facebook/react/blob/d58c07b563a79bd706531278cb4afec6292b84a8/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr.expect.md). @josephsavona can you confirm if that's the case and if we're able to drop this restriction now? (or alternatively, is there another case we can ignore?)

Nathan committed Oct 20, 2025 at 16:52 UTC ea0c17b0952e2b866a1f0dd4b5ac28c7df9d8518
13 files changed +235 -179
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
-14
@@ -1568,20 +1568,6 @@ function lowerObjectPropertyKey(
1568 name: key.node.value,
1569 };
1570 } else if (property.node.computed && key.isExpression()) {
1571 - if (!key.isIdentifier() && !key.isMemberExpression()) {
1572 - /*
1573 - * NOTE: allowing complex key expressions can trigger a bug where a mutation is made conditional
1574 - * see fixture
1575 - * error.object-expression-computed-key-modified-during-after-construction.js
1576 - */
1577 - builder.errors.push({
1578 - reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
1579 - category: ErrorCategory.Todo,
1580 - loc: key.node.loc ?? null,
1581 - suggestions: null,
1582 - });
1583 - return null;
1584 - }
1571 const place = lowerExpressionToTemporary(builder, key);
1572 return {
1573 kind: 'computed',
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md deleted
-41
@@ -1,41 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 -
7 -function Component(props) {
8 - const key = {};
9 - const context = {
10 - [(mutate(key), key)]: identity([props.value]),
11 - };
12 - mutate(key);
13 - return context;
14 -}
15 -
16 -export const FIXTURE_ENTRYPOINT = {
17 - fn: Component,
18 - params: [{value: 42}],
19 -};
20 -
21 -```
22 -
23 -
24 -## Error
25 -
26 -```
27 -Found 1 error:
28 -
29 -Todo: (BuildHIR::lowerExpression) Expected Identifier, got SequenceExpression key in ObjectExpression
30 -
31 -error.todo-object-expression-computed-key-modified-during-after-construction-sequence-expr.ts:6:6
32 - 4 | const key = {};
33 - 5 | const context = {
34 -> 6 | [(mutate(key), key)]: identity([props.value]),
35 - | ^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got SequenceExpression key in ObjectExpression
36 - 7 | };
37 - 8 | mutate(key);
38 - 9 | return context;
39 -```
40 -
41 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-modified-during-after-construction.expect.md deleted
-41
@@ -1,41 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 -
7 -function Component(props) {
8 - const key = {};
9 - const context = {
10 - [mutateAndReturn(key)]: identity([props.value]),
11 - };
12 - mutate(key);
13 - return context;
14 -}
15 -
16 -export const FIXTURE_ENTRYPOINT = {
17 - fn: Component,
18 - params: [{value: 42}],
19 -};
20 -
21 -```
22 -
23 -
24 -## Error
25 -
26 -```
27 -Found 1 error:
28 -
29 -Todo: (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
30 -
31 -error.todo-object-expression-computed-key-modified-during-after-construction.ts:6:5
32 - 4 | const key = {};
33 - 5 | const context = {
34 -> 6 | [mutateAndReturn(key)]: identity([props.value]),
35 - | ^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
36 - 7 | };
37 - 8 | mutate(key);
38 - 9 | return context;
39 -```
40 -
41 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-computed-key-mutate-key-while-constructing-object.expect.md deleted
-40
@@ -1,40 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 -
7 -function Component(props) {
8 - const key = {};
9 - const context = {
10 - [mutateAndReturn(key)]: identity([props.value]),
11 - };
12 - return context;
13 -}
14 -
15 -export const FIXTURE_ENTRYPOINT = {
16 - fn: Component,
17 - params: [{value: 42}],
18 -};
19 -
20 -```
21 -
22 -
23 -## Error
24 -
25 -```
26 -Found 1 error:
27 -
28 -Todo: (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
29 -
30 -error.todo-object-expression-computed-key-mutate-key-while-constructing-object.ts:6:5
31 - 4 | const key = {};
32 - 5 | const context = {
33 -> 6 | [mutateAndReturn(key)]: identity([props.value]),
34 - | ^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
35 - 7 | };
36 - 8 | return context;
37 - 9 | }
38 -```
39 -
40 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-object-expression-member-expr-call.expect.md deleted
-42
@@ -1,42 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 -
7 -function Component(props) {
8 - const obj = {mutateAndReturn};
9 - const key = {};
10 - const context = {
11 - [obj.mutateAndReturn(key)]: identity([props.value]),
12 - };
13 - mutate(key);
14 - return context;
15 -}
16 -
17 -export const FIXTURE_ENTRYPOINT = {
18 - fn: Component,
19 - params: [{value: 42}],
20 -};
21 -
22 -```
23 -
24 -
25 -## Error
26 -
27 -```
28 -Found 1 error:
29 -
30 -Todo: (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
31 -
32 -error.todo-object-expression-member-expr-call.ts:7:5
33 - 5 | const key = {};
34 - 6 | const context = {
35 -> 7 | [obj.mutateAndReturn(key)]: identity([props.value]),
36 - | ^^^^^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerExpression) Expected Identifier, got CallExpression key in ObjectExpression
37 - 8 | };
38 - 9 | mutate(key);
39 - 10 | return context;
40 -```
41 -
42 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.expect.md new
+56
@@ -0,0 +1,56 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 +
7 +function Component(props) {
8 + const key = {};
9 + const context = {
10 + [(mutate(key), key)]: identity([props.value]),
11 + };
12 + mutate(key);
13 + return [context, key];
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{value: 42}],
19 + sequentialRenders: [{value: 42}, {value: 42}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime";
28 +import { identity, mutate, mutateAndReturn } from "shared-runtime";
29 +
30 +function Component(props) {
31 + const $ = _c(2);
32 + let t0;
33 + if ($[0] !== props.value) {
34 + const key = {};
35 + const context = { [(mutate(key), key)]: identity([props.value]) };
36 + mutate(key);
37 + t0 = [context, key];
38 + $[0] = props.value;
39 + $[1] = t0;
40 + } else {
41 + t0 = $[1];
42 + }
43 + return t0;
44 +}
45 +
46 +export const FIXTURE_ENTRYPOINT = {
47 + fn: Component,
48 + params: [{ value: 42 }],
49 + sequentialRenders: [{ value: 42 }, { value: 42 }],
50 +};
51 +
52 +```
53 +
54 +### Eval output
55 +(kind: ok) [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
56 +[{"[object Object]":[42]},{"wat0":"joe","wat1":"joe"}]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction-sequence-expr.js renamed
+2 -1
@@ -6,10 +6,11 @@ function Component(props) {
6 [(mutate(key), key)]: identity([props.value]),
7 };
8 mutate(key);
9 - return context;
9 + return [context, key];
10 }
11
12 export const FIXTURE_ENTRYPOINT = {
13 fn: Component,
14 params: [{value: 42}],
15 + sequentialRenders: [{value: 42}, {value: 42}],
16 };
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.expect.md new
+55
@@ -0,0 +1,55 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 +
7 +function Component(props) {
8 + const key = {};
9 + const context = {
10 + [mutateAndReturn(key)]: identity([props.value]),
11 + };
12 + mutate(key);
13 + return context;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: Component,
18 + params: [{value: 42}],
19 + sequentialRenders: [{value: 42}, {value: 42}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime";
28 +import { identity, mutate, mutateAndReturn } from "shared-runtime";
29 +
30 +function Component(props) {
31 + const $ = _c(2);
32 + let context;
33 + if ($[0] !== props.value) {
34 + const key = {};
35 + context = { [mutateAndReturn(key)]: identity([props.value]) };
36 + mutate(key);
37 + $[0] = props.value;
38 + $[1] = context;
39 + } else {
40 + context = $[1];
41 + }
42 + return context;
43 +}
44 +
45 +export const FIXTURE_ENTRYPOINT = {
46 + fn: Component,
47 + params: [{ value: 42 }],
48 + sequentialRenders: [{ value: 42 }, { value: 42 }],
49 +};
50 +
51 +```
52 +
53 +### Eval output
54 +(kind: ok) {"[object Object]":[42]}
55 +{"[object Object]":[42]}
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-modified-during-after-construction.js renamed
+1
@@ -12,4 +12,5 @@ function Component(props) {
12 export const FIXTURE_ENTRYPOINT = {
13 fn: Component,
14 params: [{value: 42}],
15 + sequentialRenders: [{value: 42}, {value: 42}],
16 };
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.expect.md new
+67
@@ -0,0 +1,67 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 +
7 +function Component(props) {
8 + const key = {};
9 + const context = {
10 + [mutateAndReturn(key)]: identity([props.value]),
11 + };
12 + return context;
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{value: 42}],
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { c as _c } from "react/compiler-runtime";
26 +import { identity, mutate, mutateAndReturn } from "shared-runtime";
27 +
28 +function Component(props) {
29 + const $ = _c(5);
30 + let t0;
31 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 + const key = {};
33 +
34 + t0 = mutateAndReturn(key);
35 + $[0] = t0;
36 + } else {
37 + t0 = $[0];
38 + }
39 + let t1;
40 + if ($[1] !== props.value) {
41 + t1 = identity([props.value]);
42 + $[1] = props.value;
43 + $[2] = t1;
44 + } else {
45 + t1 = $[2];
46 + }
47 + let t2;
48 + if ($[3] !== t1) {
49 + t2 = { [t0]: t1 };
50 + $[3] = t1;
51 + $[4] = t2;
52 + } else {
53 + t2 = $[4];
54 + }
55 + const context = t2;
56 + return context;
57 +}
58 +
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: Component,
61 + params: [{ value: 42 }],
62 +};
63 +
64 +```
65 +
66 +### Eval output
67 +(kind: ok) {"[object Object]":[42]}
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-computed-key-mutate-key-while-constructing-object.js renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.expect.md new
+54
@@ -0,0 +1,54 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {identity, mutate, mutateAndReturn} from 'shared-runtime';
6 +
7 +function Component(props) {
8 + const obj = {mutateAndReturn};
9 + const key = {};
10 + const context = {
11 + [obj.mutateAndReturn(key)]: identity([props.value]),
12 + };
13 + mutate(key);
14 + return context;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{value: 42}],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime";
28 +import { identity, mutate, mutateAndReturn } from "shared-runtime";
29 +
30 +function Component(props) {
31 + const $ = _c(2);
32 + let context;
33 + if ($[0] !== props.value) {
34 + const obj = { mutateAndReturn };
35 + const key = {};
36 + context = { [obj.mutateAndReturn(key)]: identity([props.value]) };
37 + mutate(key);
38 + $[0] = props.value;
39 + $[1] = context;
40 + } else {
41 + context = $[1];
42 + }
43 + return context;
44 +}
45 +
46 +export const FIXTURE_ENTRYPOINT = {
47 + fn: Component,
48 + params: [{ value: 42 }],
49 +};
50 +
51 +```
52 +
53 +### Eval output
54 +(kind: ok) {"[object Object]":[42]}
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/object-expression-member-expr-call.js renamed