mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler][fix] mutableOnlyIfOperandsAreMutable does not apply when operands are globals (#32695)
Globals, module locals, and other locally defined functions may mutate their arguments. See test fixtures for details --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32695). * #32698 * #32697 * #32696 * __->__ #32695
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
BuiltInArrayId,
|
||||
BuiltInFireId,
|
||||
BuiltInMixedReadonlyId,
|
||||
BuiltInObjectId,
|
||||
BuiltInUseActionStateId,
|
||||
BuiltInUseContextHookId,
|
||||
BuiltInUseEffectHookId,
|
||||
@@ -45,21 +46,17 @@ export const DEFAULT_SHAPES: ShapeRegistry = new Map(BUILTIN_SHAPES);
|
||||
|
||||
// Hack until we add ObjectShapes for all globals
|
||||
const UNTYPED_GLOBALS: Set<string> = new Set([
|
||||
'String',
|
||||
'Object',
|
||||
'Function',
|
||||
'Number',
|
||||
'RegExp',
|
||||
'Date',
|
||||
'Error',
|
||||
'Function',
|
||||
'TypeError',
|
||||
'RangeError',
|
||||
'ReferenceError',
|
||||
'SyntaxError',
|
||||
'URIError',
|
||||
'EvalError',
|
||||
'Boolean',
|
||||
'DataView',
|
||||
'Float32Array',
|
||||
'Float64Array',
|
||||
@@ -75,16 +72,8 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
|
||||
'Uint32Array',
|
||||
'ArrayBuffer',
|
||||
'JSON',
|
||||
'parseFloat',
|
||||
'parseInt',
|
||||
'console',
|
||||
'isNaN',
|
||||
'eval',
|
||||
'isFinite',
|
||||
'encodeURI',
|
||||
'decodeURI',
|
||||
'encodeURIComponent',
|
||||
'decodeURIComponent',
|
||||
]);
|
||||
|
||||
const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
@@ -101,6 +90,23 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
[
|
||||
/**
|
||||
* Object.fromEntries(iterable)
|
||||
* iterable: An iterable, such as an Array or Map, containing a list of
|
||||
* objects. Each object should have two properties.
|
||||
* Returns a new object whose properties are given by the entries of the
|
||||
* iterable.
|
||||
*/
|
||||
'fromEntries',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [Effect.ConditionallyMutate],
|
||||
restParam: null,
|
||||
returnType: {kind: 'Object', shapeId: BuiltInObjectId},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
}),
|
||||
],
|
||||
]),
|
||||
],
|
||||
[
|
||||
@@ -372,6 +378,86 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'parseInt',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'parseFloat',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'isNaN',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'isFinite',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'encodeURI',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'encodeURIComponent',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'decodeURI',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'decodeURIComponent',
|
||||
addFunction(DEFAULT_SHAPES, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: {kind: 'Primitive'},
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
}),
|
||||
],
|
||||
// TODO: rest of Global objects
|
||||
];
|
||||
|
||||
|
||||
+47
-7
@@ -251,7 +251,7 @@ type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
|
||||
|
||||
// Maintains a mapping of top-level variables to the kind of value they hold
|
||||
class InferenceState {
|
||||
#env: Environment;
|
||||
env: Environment;
|
||||
|
||||
// The kind of each value, based on its allocation site
|
||||
#values: Map<InstructionValue, AbstractValue>;
|
||||
@@ -267,7 +267,7 @@ class InferenceState {
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<InstructionValue>>,
|
||||
) {
|
||||
this.#env = env;
|
||||
this.env = env;
|
||||
this.#values = values;
|
||||
this.#variables = variables;
|
||||
}
|
||||
@@ -409,8 +409,8 @@ class InferenceState {
|
||||
});
|
||||
if (
|
||||
value.kind === 'FunctionExpression' &&
|
||||
(this.#env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||
this.#env.config.enableTransitivelyFreezeFunctionExpressions)
|
||||
(this.env.config.enablePreserveExistingMemoizationGuarantees ||
|
||||
this.env.config.enableTransitivelyFreezeFunctionExpressions)
|
||||
) {
|
||||
for (const operand of value.loweredFunc.func.context) {
|
||||
const operandValues = this.#variables.get(operand.identifier.id);
|
||||
@@ -590,7 +590,7 @@ class InferenceState {
|
||||
return null;
|
||||
} else {
|
||||
return new InferenceState(
|
||||
this.#env,
|
||||
this.env,
|
||||
nextValues ?? new Map(this.#values),
|
||||
nextVariables ?? new Map(this.#variables),
|
||||
);
|
||||
@@ -604,7 +604,7 @@ class InferenceState {
|
||||
*/
|
||||
clone(): InferenceState {
|
||||
return new InferenceState(
|
||||
this.#env,
|
||||
this.env,
|
||||
new Map(this.#values),
|
||||
new Map(this.#variables),
|
||||
);
|
||||
@@ -2012,6 +2012,32 @@ export function getFunctionEffects(
|
||||
return results;
|
||||
}
|
||||
|
||||
export function isKnownMutableEffect(effect: Effect): boolean {
|
||||
switch (effect) {
|
||||
case Effect.Store:
|
||||
case Effect.ConditionallyMutate:
|
||||
case Effect.Mutate: {
|
||||
return true;
|
||||
}
|
||||
|
||||
case Effect.Unknown: {
|
||||
CompilerError.invariant(false, {
|
||||
reason: 'Unexpected unknown effect',
|
||||
description: null,
|
||||
loc: GeneratedSource,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
case Effect.Read:
|
||||
case Effect.Capture:
|
||||
case Effect.Freeze: {
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(effect, `Unexpected effect \`${effect}\``);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if all of the arguments are both non-mutable (immutable or frozen)
|
||||
* _and_ are not functions which might mutate their arguments. Note that function
|
||||
@@ -2023,10 +2049,20 @@ function areArgumentsImmutableAndNonMutating(
|
||||
args: MethodCall['args'],
|
||||
): boolean {
|
||||
for (const arg of args) {
|
||||
if (arg.kind === 'Identifier' && arg.identifier.type.kind === 'Function') {
|
||||
const fnShape = state.env.getFunctionSignature(arg.identifier.type);
|
||||
if (fnShape != null) {
|
||||
return (
|
||||
!fnShape.positionalParams.some(isKnownMutableEffect) &&
|
||||
(fnShape.restParam == null ||
|
||||
!isKnownMutableEffect(fnShape.restParam))
|
||||
);
|
||||
}
|
||||
}
|
||||
const place = arg.kind === 'Identifier' ? arg : arg.place;
|
||||
|
||||
const kind = state.kind(place).kind;
|
||||
switch (kind) {
|
||||
case ValueKind.Global:
|
||||
case ValueKind.Primitive:
|
||||
case ValueKind.Frozen: {
|
||||
/*
|
||||
@@ -2037,6 +2073,10 @@ function areArgumentsImmutableAndNonMutating(
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
/**
|
||||
* Globals, module locals, and other locally defined functions may
|
||||
* mutate their arguments.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-10
@@ -8,7 +8,12 @@ function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
return <Stringify>{derived.at(-1)}</Stringify>;
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -26,28 +31,42 @@ import { c as _c } from "react/compiler-runtime";
|
||||
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(4);
|
||||
const $ = _c(7);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(-1);
|
||||
t1 = derived.at(0);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== t1) {
|
||||
t2 = <Stringify>{t1}</Stringify>;
|
||||
$[2] = t1;
|
||||
if ($[2] !== derived) {
|
||||
t2 = derived.at(-1);
|
||||
$[2] = derived;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
let t3;
|
||||
if ($[4] !== t1 || $[5] !== t2) {
|
||||
t3 = (
|
||||
<Stringify>
|
||||
{t1}
|
||||
{t2}
|
||||
</Stringify>
|
||||
);
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
@@ -59,6 +78,6 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":{"value":5,"wat0":"joe"}}</div>
|
||||
<div>{"children":{"value":6,"wat0":"joe"}}</div>
|
||||
<div>{"children":{"value":6,"wat0":"joe"}}</div>
|
||||
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
+6
-1
@@ -4,7 +4,12 @@ function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity();
|
||||
const derived = Array.from(arr, mutateAndReturn);
|
||||
return <Stringify>{derived.at(-1)}</Stringify>;
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity(null);
|
||||
const derived = arr.filter(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity(null);
|
||||
const derived = arr.filter(mutateAndReturn);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(0);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== derived) {
|
||||
t2 = derived.at(-1);
|
||||
$[2] = derived;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t1 || $[5] !== t2) {
|
||||
t3 = (
|
||||
<Stringify>
|
||||
{t1}
|
||||
{t2}
|
||||
</Stringify>
|
||||
);
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 5 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Repro for bug with `mutableOnlyIfOperandsAreMutable` flag
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* (kind: ok)
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* Forget:
|
||||
* (kind: ok)
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
* <div>{"children":[{"value":"foo","wat0":"joe","wat1":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity(null);
|
||||
const derived = arr.filter(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Also see repro-array-map-known-mutate-shape, which calls a global function
|
||||
* that mutates its operands.
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity(null);
|
||||
const derived = arr.filter(Boolean);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Also see repro-array-map-known-mutate-shape, which calls a global function
|
||||
* that mutates its operands.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(13);
|
||||
const { value } = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = { value: "foo" };
|
||||
t2 = { value: "bar" };
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
t2 = $[1];
|
||||
}
|
||||
let t3;
|
||||
if ($[2] !== value) {
|
||||
t3 = [t1, t2, { value }];
|
||||
$[2] = value;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
const arr = t3;
|
||||
useIdentity(null);
|
||||
let t4;
|
||||
if ($[4] !== arr) {
|
||||
t4 = arr.filter(Boolean);
|
||||
$[4] = arr;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
const derived = t4;
|
||||
let t5;
|
||||
if ($[6] !== derived) {
|
||||
t5 = derived.at(0);
|
||||
$[6] = derived;
|
||||
$[7] = t5;
|
||||
} else {
|
||||
t5 = $[7];
|
||||
}
|
||||
let t6;
|
||||
if ($[8] !== derived) {
|
||||
t6 = derived.at(-1);
|
||||
$[8] = derived;
|
||||
$[9] = t6;
|
||||
} else {
|
||||
t6 = $[9];
|
||||
}
|
||||
let t7;
|
||||
if ($[10] !== t5 || $[11] !== t6) {
|
||||
t7 = (
|
||||
<Stringify>
|
||||
{t5}
|
||||
{t6}
|
||||
</Stringify>
|
||||
);
|
||||
$[10] = t5;
|
||||
$[11] = t6;
|
||||
$[12] = t7;
|
||||
} else {
|
||||
t7 = $[12];
|
||||
}
|
||||
return t7;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 5 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":[{"value":"foo"},{"value":5}]}</div>
|
||||
<div>{"children":[{"value":"foo"},{"value":6}]}</div>
|
||||
<div>{"children":[{"value":"foo"},{"value":6}]}</div>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Also see repro-array-map-known-mutate-shape, which calls a global function
|
||||
* that mutates its operands.
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity(null);
|
||||
const derived = arr.filter(Boolean);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
|
||||
* function call which captures its callee when applying an operand.
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity(null);
|
||||
const derived = arr.map(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
|
||||
* function call which captures its callee when applying an operand.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { value } = t0;
|
||||
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
|
||||
useIdentity(null);
|
||||
const derived = arr.map(mutateAndReturn);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(0);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== derived) {
|
||||
t2 = derived.at(-1);
|
||||
$[2] = derived;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t1 || $[5] !== t2) {
|
||||
t3 = (
|
||||
<Stringify>
|
||||
{t1}
|
||||
{t2}
|
||||
</Stringify>
|
||||
);
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 5 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Copy of repro-array-map-capture-mutate-bug, showing that the same issue applies to any
|
||||
* function call which captures its callee when applying an operand.
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
|
||||
useIdentity(null);
|
||||
const derived = arr.map(mutateAndReturn);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
|
||||
* function that does *not* mutate its operands.
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [
|
||||
new Set([['foo', 2]]).values(),
|
||||
new Set([['bar', 4]]).values(),
|
||||
[['baz', value]],
|
||||
];
|
||||
useIdentity(null);
|
||||
const derived = arr.map(Object.fromEntries);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
|
||||
* function that does *not* mutate its operands.
|
||||
*/
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { value } = t0;
|
||||
const arr = [
|
||||
new Set([["foo", 2]]).values(),
|
||||
new Set([["bar", 4]]).values(),
|
||||
[["baz", value]],
|
||||
];
|
||||
|
||||
useIdentity(null);
|
||||
const derived = arr.map(Object.fromEntries);
|
||||
let t1;
|
||||
if ($[0] !== derived) {
|
||||
t1 = derived.at(0);
|
||||
$[0] = derived;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== derived) {
|
||||
t2 = derived.at(-1);
|
||||
$[2] = derived;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t1 || $[5] !== t2) {
|
||||
t3 = (
|
||||
<Stringify>
|
||||
{t1}
|
||||
{t2}
|
||||
</Stringify>
|
||||
);
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: 5 }],
|
||||
sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"children":[{"foo":2},{"baz":5}]}</div>
|
||||
<div>{"children":[{"foo":2},{"baz":6}]}</div>
|
||||
<div>{"children":[{"foo":2},{"baz":6}]}</div>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import {Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Also see repro-array-map-known-nonmutate-Boolean, which calls a global
|
||||
* function that does *not* mutate its operands.
|
||||
*/
|
||||
function Component({value}) {
|
||||
const arr = [
|
||||
new Set([['foo', 2]]).values(),
|
||||
new Set([['bar', 4]]).values(),
|
||||
[['baz', value]],
|
||||
];
|
||||
useIdentity(null);
|
||||
const derived = arr.map(Object.fromEntries);
|
||||
return (
|
||||
<Stringify>
|
||||
{derived.at(0)}
|
||||
{derived.at(-1)}
|
||||
</Stringify>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{value: 5}],
|
||||
sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
|
||||
};
|
||||
Reference in New Issue
Block a user