@samitouri / QOS-React / commits / 1c7e34cdb6

Stop reporting errors on mutations of primitives (and associated false positives)

ghstack-source-id: 4dc9b3c51953f8ed2184ebc5e438bc877959b5d0 Pull Request resolved: https://github.com/facebook/react-forget/pull/2903

Joe Savona committed Apr 25, 2024 at 09:39 UTC 1c7e34cdb623ac03f2d1373c5d533880792c148b
8 files changed +130 -73
compiler/packages/babel-plugin-react-forget/src/HIR/Globals.ts
+11 -11
@@ -89,7 +89,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
89 restParam: null,
90 returnType: { kind: "Primitive" },
91 calleeEffect: Effect.Read,
92 - returnValueKind: ValueKind.Immutable,
92 + returnValueKind: ValueKind.Primitive,
93 }),
94 ],
95 /*
@@ -130,7 +130,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
130 restParam: Effect.Read,
131 returnType: { kind: "Primitive" },
132 calleeEffect: Effect.Read,
133 - returnValueKind: ValueKind.Immutable,
133 + returnValueKind: ValueKind.Primitive,
134 }),
135 ],
136 ]),
@@ -147,7 +147,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
147 restParam: Effect.Read,
148 returnType: { kind: "Primitive" },
149 calleeEffect: Effect.Read,
150 - returnValueKind: ValueKind.Immutable,
150 + returnValueKind: ValueKind.Primitive,
151 }),
152 ],
153 [
@@ -157,7 +157,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
157 restParam: Effect.Read,
158 returnType: { kind: "Primitive" },
159 calleeEffect: Effect.Read,
160 - returnValueKind: ValueKind.Immutable,
160 + returnValueKind: ValueKind.Primitive,
161 }),
162 ],
163 [
@@ -167,7 +167,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
167 restParam: Effect.Read,
168 returnType: { kind: "Primitive" },
169 calleeEffect: Effect.Read,
170 - returnValueKind: ValueKind.Immutable,
170 + returnValueKind: ValueKind.Primitive,
171 }),
172 ],
173 [
@@ -177,7 +177,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
177 restParam: Effect.Read,
178 returnType: { kind: "Primitive" },
179 calleeEffect: Effect.Read,
180 - returnValueKind: ValueKind.Immutable,
180 + returnValueKind: ValueKind.Primitive,
181 }),
182 ],
183 [
@@ -187,7 +187,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
187 restParam: Effect.Read,
188 returnType: { kind: "Primitive" },
189 calleeEffect: Effect.Read,
190 - returnValueKind: ValueKind.Immutable,
190 + returnValueKind: ValueKind.Primitive,
191 }),
192 ],
193 [
@@ -197,7 +197,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
197 restParam: Effect.Read,
198 returnType: { kind: "Primitive" },
199 calleeEffect: Effect.Read,
200 - returnValueKind: ValueKind.Immutable,
200 + returnValueKind: ValueKind.Primitive,
201 }),
202 ],
203 ]),
@@ -209,7 +209,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
209 restParam: Effect.Read,
210 returnType: { kind: "Primitive" },
211 calleeEffect: Effect.Read,
212 - returnValueKind: ValueKind.Immutable,
212 + returnValueKind: ValueKind.Primitive,
213 }),
214 ],
215 [
@@ -219,7 +219,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
219 restParam: Effect.Read,
220 returnType: { kind: "Primitive" },
221 calleeEffect: Effect.Read,
222 - returnValueKind: ValueKind.Immutable,
222 + returnValueKind: ValueKind.Primitive,
223 }),
224 ],
225 [
@@ -229,7 +229,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
229 restParam: Effect.Read,
230 returnType: { kind: "Primitive" },
231 calleeEffect: Effect.Read,
232 - returnValueKind: ValueKind.Immutable,
232 + returnValueKind: ValueKind.Primitive,
233 }),
234 ],
235 // TODO: rest of Global objects
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+2 -1
@@ -1244,7 +1244,8 @@ export enum ValueReason {
1244 export enum ValueKind {
1245 MaybeFrozen = "maybefrozen",
1246 Frozen = "frozen",
1247 - Immutable = "immutable",
1247 + Primitive = "primitive",
1248 + Global = "global",
1249 Mutable = "mutable",
1250 Context = "context",
1251 }
compiler/packages/babel-plugin-react-forget/src/HIR/ObjectShape.ts
+8 -8
@@ -235,7 +235,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
235 restParam: Effect.Capture,
236 returnType: PRIMITIVE_TYPE,
237 calleeEffect: Effect.Store,
238 - returnValueKind: ValueKind.Immutable,
238 + returnValueKind: ValueKind.Primitive,
239 }),
240 ],
241 [
@@ -284,7 +284,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
284 * the array object itself is not modified
285 */
286 calleeEffect: Effect.ConditionallyMutate,
287 - returnValueKind: ValueKind.Immutable,
287 + returnValueKind: ValueKind.Primitive,
288 noAlias: true,
289 mutableOnlyIfOperandsAreMutable: true,
290 }),
@@ -301,7 +301,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
301 * the array object itself is not modified
302 */
303 calleeEffect: Effect.ConditionallyMutate,
304 - returnValueKind: ValueKind.Immutable,
304 + returnValueKind: ValueKind.Primitive,
305 noAlias: true,
306 mutableOnlyIfOperandsAreMutable: true,
307 }),
@@ -330,7 +330,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
330 * the array object itself is not modified
331 */
332 calleeEffect: Effect.ConditionallyMutate,
333 - returnValueKind: ValueKind.Immutable,
333 + returnValueKind: ValueKind.Primitive,
334 noAlias: true,
335 mutableOnlyIfOperandsAreMutable: true,
336 }),
@@ -342,7 +342,7 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
342 restParam: Effect.ConditionallyMutate,
343 returnType: PRIMITIVE_TYPE,
344 calleeEffect: Effect.Read,
345 - returnValueKind: ValueKind.Immutable,
345 + returnValueKind: ValueKind.Primitive,
346 }),
347 ],
348 // TODO: rest of Array properties
@@ -357,7 +357,7 @@ addObject(BUILTIN_SHAPES, BuiltInObjectId, [
357 restParam: null,
358 returnType: PRIMITIVE_TYPE,
359 calleeEffect: Effect.Read,
360 - returnValueKind: ValueKind.Immutable,
360 + returnValueKind: ValueKind.Primitive,
361 }),
362 ],
363 /*
@@ -378,7 +378,7 @@ addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
378 restParam: Effect.Freeze,
379 returnType: PRIMITIVE_TYPE,
380 calleeEffect: Effect.Read,
381 - returnValueKind: ValueKind.Immutable,
381 + returnValueKind: ValueKind.Primitive,
382 },
383 BuiltInSetStateId
384 ),
@@ -401,7 +401,7 @@ addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
401 restParam: Effect.Read,
402 returnType: PRIMITIVE_TYPE,
403 calleeEffect: Effect.Read,
404 - returnValueKind: ValueKind.Immutable,
404 + returnValueKind: ValueKind.Primitive,
405 }),
406 ],
407 [
compiler/packages/babel-plugin-react-forget/src/Inference/InferReferenceEffects.ts
+37 -16
@@ -531,7 +531,11 @@ class InferenceState {
531 ? new Set([place])
532 : valueKind.context,
533 };
534 - } else if (valueKind.kind !== ValueKind.Mutable) {
534 + } else if (
535 + valueKind.kind !== ValueKind.Mutable &&
536 + // We ignore mutations of primitives since this is not a React-specific problem
537 + valueKind.kind !== ValueKind.Primitive
538 + ) {
539 let reason = getWriteErrorReason(valueKind);
540 functionEffect = {
541 kind:
@@ -566,7 +570,11 @@ class InferenceState {
570 ? new Set([place])
571 : valueKind.context,
572 };
569 - } else if (valueKind.kind !== ValueKind.Mutable) {
573 + } else if (
574 + valueKind.kind !== ValueKind.Mutable &&
575 + // We ignore mutations of primitives since this is not a React-specific problem
576 + valueKind.kind !== ValueKind.Primitive
577 + ) {
578 let reason = getWriteErrorReason(valueKind);
579 functionEffect = {
580 kind:
@@ -601,7 +609,8 @@ class InferenceState {
609 }
610 case Effect.Capture: {
611 if (
604 - valueKind.kind === ValueKind.Immutable ||
612 + valueKind.kind === ValueKind.Primitive ||
613 + valueKind.kind === ValueKind.Global ||
614 valueKind.kind === ValueKind.Frozen ||
615 valueKind.kind === ValueKind.MaybeFrozen
616 ) {
@@ -876,9 +885,20 @@ function mergeValues(a: ValueKind, b: ValueKind): ValueKind {
885 // context | immutable
886 return ValueKind.Context;
887 }
879 - } else {
880 - // frozen | immutable
888 + } else if (a === ValueKind.Frozen || b === ValueKind.Frozen) {
889 return ValueKind.Frozen;
890 + } else if (a === ValueKind.Global || b === ValueKind.Global) {
891 + return ValueKind.Global;
892 + } else {
893 + CompilerError.invariant(
894 + a === ValueKind.Primitive && b == ValueKind.Primitive,
895 + {
896 + reason: `Unexpected value kind in mergeValues()`,
897 + description: `Found kinds ${a} and ${b}`,
898 + loc: GeneratedSource,
899 + }
900 + );
901 + return ValueKind.Primitive;
902 }
903 }
904
@@ -940,7 +960,7 @@ function inferBlock(
960 switch (instrValue.kind) {
961 case "BinaryExpression": {
962 valueKind = {
943 - kind: ValueKind.Immutable,
963 + kind: ValueKind.Primitive,
964 reason: new Set([ValueReason.Other]),
965 context: new Set(),
966 };
@@ -1066,7 +1086,7 @@ function inferBlock(
1086 }
1087 case "UnaryExpression": {
1088 valueKind = {
1069 - kind: ValueKind.Immutable,
1089 + kind: ValueKind.Primitive,
1090 reason: new Set([ValueReason.Other]),
1091 context: new Set(),
1092 };
@@ -1121,7 +1141,7 @@ function inferBlock(
1141 * an immutable string
1142 */
1143 valueKind = {
1124 - kind: ValueKind.Immutable,
1144 + kind: ValueKind.Primitive,
1145 reason: new Set([ValueReason.Other]),
1146 context: new Set(),
1147 };
@@ -1143,7 +1163,7 @@ function inferBlock(
1163 }
1164 case "LoadGlobal":
1165 valueKind = {
1146 - kind: ValueKind.Immutable,
1166 + kind: ValueKind.Global,
1167 reason: new Set([ValueReason.Global]),
1168 context: new Set(),
1169 };
@@ -1152,7 +1172,7 @@ function inferBlock(
1172 case "JSXText":
1173 case "Primitive": {
1174 valueKind = {
1155 - kind: ValueKind.Immutable,
1175 + kind: ValueKind.Primitive,
1176 reason: new Set([ValueReason.Other]),
1177 context: new Set(),
1178 };
@@ -1454,7 +1474,7 @@ function inferBlock(
1474 case "PropertyDelete": {
1475 // `delete` returns a boolean (immutable) and modifies the object
1476 valueKind = {
1457 - kind: ValueKind.Immutable,
1477 + kind: ValueKind.Primitive,
1478 reason: new Set([ValueReason.Other]),
1479 context: new Set(),
1480 };
@@ -1517,7 +1537,7 @@ function inferBlock(
1537 functionEffects
1538 );
1539 state.initialize(instrValue, {
1520 - kind: ValueKind.Immutable,
1540 + kind: ValueKind.Primitive,
1541 reason: new Set([ValueReason.Other]),
1542 context: new Set(),
1543 });
@@ -1605,7 +1625,7 @@ function inferBlock(
1625 const lvalue = instr.lvalue;
1626 lvalue.effect = Effect.ConditionallyMutate;
1627 state.initialize(instrValue, {
1608 - kind: ValueKind.Immutable,
1628 + kind: ValueKind.Frozen,
1629 reason: new Set([ValueReason.Other]),
1630 context: new Set(),
1631 });
@@ -1656,7 +1676,7 @@ function inferBlock(
1676 context: new Set(),
1677 }
1678 : {
1659 - kind: ValueKind.Immutable,
1679 + kind: ValueKind.Primitive,
1680 reason: new Set([ValueReason.Other]),
1681 context: new Set(),
1682 }
@@ -1878,7 +1898,7 @@ function inferBlock(
1898 effect = { kind: Effect.Read, reason: ValueReason.Other };
1899 lvalueEffect = Effect.Store;
1900 valueKind = {
1881 - kind: ValueKind.Immutable,
1901 + kind: ValueKind.Primitive,
1902 reason: new Set([ValueReason.Other]),
1903 context: new Set(),
1904 };
@@ -2009,7 +2029,8 @@ function areArgumentsImmutableAndNonMutating(
2029 const place = arg.kind === "Identifier" ? arg : arg.place;
2030 const kind = state.kind(place).kind;
2031 switch (kind) {
2012 - case ValueKind.Immutable:
2032 + case ValueKind.Global:
2033 + case ValueKind.Primitive:
2034 case ValueKind.Frozen: {
2035 /*
2036 * Only immutable values, or frozen lambdas are allowed.
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.invalid-mutate-props-in-effect-fixpoint.expect.md
+1 -1
@@ -28,7 +28,7 @@ function Component(props) {
28 8 | let y = x;
29 9 | let mutateProps = () => {
30 > 10 | y.foo = true;
31 - | ^ InvalidReact: This mutates a variable that React considers immutable. Found mutation of `y` (10:10)
31 + | ^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead. Found mutation of `y` (10:10)
32 11 | };
33 12 | let mutatePropsIndirect = () => {
34 13 | mutateProps();
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-for-of-capture-item-of-local-collection-mutate-later-value-initially-null.expect.md deleted
-34
@@ -1,34 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -import { makeObject_Primitives } from "shared-runtime";
6 -
7 -function Component(props) {
8 - let lastItem = null; // we reject this code bc `lastItem` could be null and you can't mutate null
9 - const items = [makeObject_Primitives(), makeObject_Primitives()];
10 - for (const x of items) {
11 - lastItem = x;
12 - }
13 - if (lastItem != null) {
14 - lastItem.mutated = true;
15 - }
16 - return items;
17 -}
18 -
19 -```
20 -
21 -
22 -## Error
23 -
24 -```
25 - 8 | }
26 - 9 | if (lastItem != null) {
27 -> 10 | lastItem.mutated = true;
28 - | ^^^^^^^^ InvalidReact: This mutates a variable that React considers immutable (10:10)
29 - 11 | }
30 - 12 | return items;
31 - 13 | }
32 -```
33 -
34 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-of-capture-item-of-local-collection-mutate-later-value-initially-null.expect.md new
+63
@@ -0,0 +1,63 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import { makeObject_Primitives } from "shared-runtime";
6 +
7 +function Component(props) {
8 + let lastItem = null;
9 + const items = [makeObject_Primitives(), makeObject_Primitives()];
10 + for (const x of items) {
11 + lastItem = x;
12 + }
13 + if (lastItem != null) {
14 + lastItem.a += 1;
15 + }
16 + return items;
17 +}
18 +
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: Component,
21 + params: [{}],
22 + sequentialRenders: [{}, {}, {}],
23 +};
24 +
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +import { unstable_useMemoCache as useMemoCache } from "react";
31 +import { makeObject_Primitives } from "shared-runtime";
32 +
33 +function Component(props) {
34 + const $ = useMemoCache(1);
35 + let items;
36 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37 + let lastItem = null;
38 + items = [makeObject_Primitives(), makeObject_Primitives()];
39 + for (const x of items) {
40 + lastItem = x;
41 + }
42 + if (lastItem != null) {
43 + lastItem.a = lastItem.a + 1;
44 + }
45 + $[0] = items;
46 + } else {
47 + items = $[0];
48 + }
49 + return items;
50 +}
51 +
52 +export const FIXTURE_ENTRYPOINT = {
53 + fn: Component,
54 + params: [{}],
55 + sequentialRenders: [{}, {}, {}],
56 +};
57 +
58 +```
59 +
60 +### Eval output
61 +(kind: ok) [{"a":0,"b":"value1","c":true},{"a":1,"b":"value1","c":true}]
62 +[{"a":0,"b":"value1","c":true},{"a":1,"b":"value1","c":true}]
63 +[{"a":0,"b":"value1","c":true},{"a":1,"b":"value1","c":true}]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-of-capture-item-of-local-collection-mutate-later-value-initially-null.js renamed
+8 -2
@@ -1,13 +1,19 @@
1 import { makeObject_Primitives } from "shared-runtime";
2
3 function Component(props) {
4 - let lastItem = null; // we reject this code bc `lastItem` could be null and you can't mutate null
4 + let lastItem = null;
5 const items = [makeObject_Primitives(), makeObject_Primitives()];
6 for (const x of items) {
7 lastItem = x;
8 }
9 if (lastItem != null) {
10 - lastItem.mutated = true;
10 + lastItem.a += 1;
11 }
12 return items;
13 }
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{}],
18 + sequentialRenders: [{}, {}, {}],
19 +};