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
+};