[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:
mofeiZ
2025-03-23 23:07:24 -04:00
committed by GitHub
parent 4a9df08157
commit febc09b480
12 changed files with 709 additions and 30 deletions
@@ -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
];
@@ -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;
}
}
@@ -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>
@@ -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 = {
@@ -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>
@@ -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}],
};
@@ -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>
@@ -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}],
};
@@ -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>
@@ -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}],
};
@@ -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>
@@ -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}],
};