diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index 68f8e199db..37a8816d0b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -65,8 +65,6 @@ const UNTYPED_GLOBALS: Set = new Set([ 'Int8Array', 'Int16Array', 'Int32Array', - 'Map', - 'Set', 'WeakMap', 'Uint8Array', 'Uint8ClampedArray', @@ -140,7 +138,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ 'from', addFunction(DEFAULT_SHAPES, [], { positionalParams: [ - Effect.ConditionallyMutate, + Effect.ConditionallyMutateIterator, Effect.ConditionallyMutate, Effect.ConditionallyMutate, ], @@ -466,7 +464,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ DEFAULT_SHAPES, [], { - positionalParams: [Effect.ConditionallyMutate], + positionalParams: [Effect.ConditionallyMutateIterator], restParam: null, returnType: {kind: 'Object', shapeId: BuiltInMapId}, calleeEffect: Effect.Read, @@ -482,7 +480,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ DEFAULT_SHAPES, [], { - positionalParams: [Effect.ConditionallyMutate], + positionalParams: [Effect.ConditionallyMutateIterator], restParam: null, returnType: {kind: 'Object', shapeId: BuiltInSetId}, calleeEffect: Effect.Read, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index d2e9898906..f58cdfbb08 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1396,6 +1396,7 @@ export enum Effect { Read = 'read', // This reference reads and stores the value Capture = 'capture', + ConditionallyMutateIterator = 'mutate-iterator?', /* * This reference *may* write to (mutate) the value. This covers two similar cases: * - The compiler is being conservative and assuming that a value *may* be mutated @@ -1414,11 +1415,11 @@ export enum Effect { // This reference may alias to (mutate) the value Store = 'store', } - export const EffectSchema = z.enum([ Effect.Read, Effect.Mutate, Effect.ConditionallyMutate, + Effect.ConditionallyMutateIterator, Effect.Capture, Effect.Store, Effect.Freeze, @@ -1432,6 +1433,7 @@ export function isMutableEffect( case Effect.Capture: case Effect.Store: case Effect.ConditionallyMutate: + case Effect.ConditionallyMutateIterator: case Effect.Mutate: { return true; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts index 508a970d93..45b5462efb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts @@ -11,7 +11,10 @@ import { Identifier, InstructionId, InstructionKind, + isArrayType, + isMapType, isRefOrRefValue, + isSetType, makeInstructionId, Place, } from '../HIR/HIR'; @@ -90,6 +93,17 @@ function inferPlace( infer(place, instrId); } return; + case Effect.ConditionallyMutateIterator: { + const identifier = place.identifier; + if ( + !isArrayType(identifier) && + !isSetType(identifier) && + !isMapType(identifier) + ) { + infer(place, instrId); + } + return; + } case Effect.ConditionallyMutate: case Effect.Mutate: { infer(place, instrId); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index 344949b020..e2deab15db 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -230,6 +230,7 @@ export function inferReactivePlaces(fn: HIRFunction): void { case Effect.Capture: case Effect.Store: case Effect.ConditionallyMutate: + case Effect.ConditionallyMutateIterator: case Effect.Mutate: { if (isMutable(instruction, operand)) { reactiveIdentifiers.markReactive(operand); diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts index 30a8892365..7db4327b04 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -29,8 +29,10 @@ import { ValueKind, ValueReason, isArrayType, + isMapType, isMutableEffect, isObjectType, + isSetType, } from '../HIR/HIR'; import {FunctionSignature} from '../HIR/ObjectShape'; import { @@ -470,6 +472,25 @@ class InferenceState { } break; } + case Effect.ConditionallyMutateIterator: { + if ( + valueKind.kind === ValueKind.Mutable || + valueKind.kind === ValueKind.Context + ) { + if ( + isArrayType(place.identifier) || + isSetType(place.identifier) || + isMapType(place.identifier) + ) { + effect = Effect.Capture; + } else { + effect = Effect.ConditionallyMutate; + } + } else { + effect = Effect.Read; + } + break; + } case Effect.Mutate: { effect = Effect.Mutate; break; @@ -881,9 +902,7 @@ function inferBlock( state.referenceAndRecordEffects( freezeActions, element.place, - isArrayType(element.place.identifier) - ? Effect.Capture - : Effect.ConditionallyMutate, + Effect.ConditionallyMutateIterator, ValueReason.Other, ); } else if (element.kind === 'Identifier') { @@ -1644,7 +1663,13 @@ function inferBlock( kind === ValueKind.Mutable || kind === ValueKind.Context; let effect; let valueKind: AbstractValue; - if (!isMutable || isArrayType(instrValue.collection.identifier)) { + const iterator = instrValue.collection.identifier; + if ( + !isMutable || + isArrayType(iterator) || + isMapType(iterator) || + isSetType(iterator) + ) { // Case 1, assume iterator is a separate mutable object effect = { kind: Effect.Read, @@ -1685,7 +1710,7 @@ function inferBlock( state.referenceAndRecordEffects( freezeActions, instrValue.iterator, - Effect.ConditionallyMutate, + Effect.ConditionallyMutateIterator, ValueReason.Other, ); /** @@ -1847,6 +1872,7 @@ export function isKnownMutableEffect(effect: Effect): boolean { switch (effect) { case Effect.Store: case Effect.ConditionallyMutate: + case Effect.ConditionallyMutateIterator: case Effect.Mutate: { return true; } @@ -1950,7 +1976,7 @@ function getArgumentEffect( }); } // effects[i] is Effect.Capture | Effect.Read | Effect.Store - return Effect.ConditionallyMutate; + return Effect.ConditionallyMutateIterator; } } else { return Effect.ConditionallyMutate; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md index 8892c8d484..12be224d8f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md @@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime"; * (2) the 1st argument might mutate its callee */ function Component(t0) { - const $ = _c(4); + const $ = _c(10); const { value } = t0; - const arr = [{ value: "foo" }, { value: "bar" }, { value }]; - useIdentity(); - const derived = Array.from(arr, _temp); let t1; - if ($[0] !== derived) { - t1 = derived.at(-1); - $[0] = derived; - $[1] = t1; - } else { - t1 = $[1]; - } let t2; - if ($[2] !== t1) { - t2 = {t1}; - $[2] = t1; - $[3] = t2; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = { value: "foo" }; + t2 = { value: "bar" }; + $[0] = t1; + $[1] = t2; } else { - t2 = $[3]; + t1 = $[0]; + t2 = $[1]; } - return t2; + let t3; + if ($[2] !== value) { + t3 = [t1, t2, { value }]; + $[2] = value; + $[3] = t3; + } else { + t3 = $[3]; + } + const arr = t3; + useIdentity(); + let t4; + if ($[4] !== arr) { + t4 = Array.from(arr, _temp); + $[4] = arr; + $[5] = t4; + } else { + t4 = $[5]; + } + const derived = t4; + let t5; + if ($[6] !== derived) { + t5 = derived.at(-1); + $[6] = derived; + $[7] = t5; + } else { + t5 = $[7]; + } + let t6; + if ($[8] !== t5) { + t6 = {t5}; + $[8] = t5; + $[9] = t6; + } else { + t6 = $[9]; + } + return t6; } function _temp(x, idx) { return { ...x, id: idx }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md index 66d0b42584..3e89dbeae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md @@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime"; * (2) the 1st argument might mutate its callee */ function Component(t0) { - const $ = _c(4); + const $ = _c(10); const { value } = t0; - const arr = [{ value: "foo" }, { value: "bar" }, { value }]; - useIdentity(); - const derived = Array.from(arr); let t1; - if ($[0] !== derived) { - t1 = derived.at(-1); - $[0] = derived; - $[1] = t1; - } else { - t1 = $[1]; - } let t2; - if ($[2] !== t1) { - t2 = {t1}; - $[2] = t1; - $[3] = t2; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = { value: "foo" }; + t2 = { value: "bar" }; + $[0] = t1; + $[1] = t2; } else { - t2 = $[3]; + t1 = $[0]; + t2 = $[1]; } - return t2; + let t3; + if ($[2] !== value) { + t3 = [t1, t2, { value }]; + $[2] = value; + $[3] = t3; + } else { + t3 = $[3]; + } + const arr = t3; + useIdentity(); + let t4; + if ($[4] !== arr) { + t4 = Array.from(arr); + $[4] = arr; + $[5] = t4; + } else { + t4 = $[5]; + } + const derived = t4; + let t5; + if ($[6] !== derived) { + t5 = derived.at(-1); + $[6] = derived; + $[7] = t5; + } else { + t5 = $[7]; + } + let t6; + if ($[8] !== t5) { + t6 = {t5}; + $[8] = t5; + $[9] = t6; + } else { + t6 = $[9]; + } + return t6; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md index 9be174d998..9421194495 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md @@ -7,7 +7,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime'; function Component({value}) { const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; useIdentity(); - const derived = Array.from(arr, mutateAndReturn); + const derived = Array.from(arr).map(mutateAndReturn); return ( {derived.at(0)} @@ -19,7 +19,7 @@ function Component({value}) { export const FIXTURE_ENTRYPOINT = { fn: Component, params: [{value: 5}], - sequentialRenders: [{value: 5}, {value: 6}, {value: 6}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}], }; ``` @@ -35,7 +35,7 @@ function Component(t0) { const { value } = t0; const arr = [{ value: "foo" }, { value: "bar" }, { value }]; useIdentity(); - const derived = Array.from(arr, mutateAndReturn); + const derived = Array.from(arr).map(mutateAndReturn); let t1; if ($[0] !== derived) { t1 = derived.at(0); @@ -72,7 +72,7 @@ function Component(t0) { export const FIXTURE_ENTRYPOINT = { fn: Component, params: [{ value: 5 }], - sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }], + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }], }; ``` @@ -80,4 +80,5 @@ export const FIXTURE_ENTRYPOINT = { ### Eval output (kind: ok)
{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}
{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}
-
{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}
\ No newline at end of file +
{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}
+
{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js index 4e224c8a9a..af5bea83a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js @@ -3,7 +3,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime'; function Component({value}) { const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; useIdentity(); - const derived = Array.from(arr, mutateAndReturn); + const derived = Array.from(arr).map(mutateAndReturn); return ( {derived.at(0)} @@ -15,5 +15,5 @@ function Component({value}) { export const FIXTURE_ENTRYPOINT = { fn: Component, params: [{value: 5}], - sequentialRenders: [{value: 5}, {value: 6}, {value: 6}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}], }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.expect.md new file mode 100644 index 0000000000..6882f8f4bd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.expect.md @@ -0,0 +1,66 @@ + +## Input + +```javascript +import {useIdentity} from 'shared-runtime'; + +/** + * Forked version of call-spread-argument-mutable-iterator that is known to not mutate + * the spread argument since it is a Set + */ +function useFoo() { + const s = new Set([1, 2]); + useIdentity(null); + return [Math.max(...s), s]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useIdentity } from "shared-runtime"; + +/** + * Forked version of call-spread-argument-mutable-iterator that is known to not mutate + * the spread argument since it is a Set + */ +function useFoo() { + const $ = _c(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = new Set([1, 2]); + $[0] = t0; + } else { + t0 = $[0]; + } + const s = t0; + useIdentity(null); + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = [Math.max(...s), s]; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +### Eval output +(kind: ok) [2,{"kind":"Set","value":[1,2]}] +[2,{"kind":"Set","value":[1,2]}] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.ts new file mode 100644 index 0000000000..b2746b0168 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.ts @@ -0,0 +1,17 @@ +import {useIdentity} from 'shared-runtime'; + +/** + * Forked version of call-spread-argument-mutable-iterator that is known to not mutate + * the spread argument since it is a Set + */ +function useFoo() { + const s = new Set([1, 2]); + useIdentity(null); + return [Math.max(...s), s]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{}], + sequentialRenders: [{}, {}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.expect.md index 3d640c5d2a..e0d675a2c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.expect.md @@ -4,7 +4,7 @@ ```javascript const MODULE_LOCAL = new Set([4, 5, 6]); function useFoo({propArr}: {propArr: Array}) { - /* TODO: Array can be memoized separately of the Set */ + /* Array can be memoized separately of the Set */ const s1 = new Set([1, 2, 3]); s1.add(propArr[0]); @@ -16,7 +16,7 @@ function useFoo({propArr}: {propArr: Array}) { s3.add(propArr[2]); /** - * TODO: s3 should be memoized separately of s4 + * s4 should be memoized separately from s3 */ const s4 = new Set(s3); s4.add(propArr[3]); @@ -37,52 +37,62 @@ export const FIXTURE_ENTRYPOINT = { import { c as _c } from "react/compiler-runtime"; const MODULE_LOCAL = new Set([4, 5, 6]); function useFoo(t0) { - const $ = _c(13); + const $ = _c(15); const { propArr } = t0; - let s1; - if ($[0] !== propArr[0]) { - s1 = new Set([1, 2, 3]); - s1.add(propArr[0]); - $[0] = propArr[0]; - $[1] = s1; + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = [1, 2, 3]; + $[0] = t1; } else { - s1 = $[1]; + t1 = $[0]; + } + let s1; + if ($[1] !== propArr[0]) { + s1 = new Set(t1); + s1.add(propArr[0]); + $[1] = propArr[0]; + $[2] = s1; + } else { + s1 = $[2]; } let s2; let s3; - let s4; - if ($[2] !== propArr[1] || $[3] !== propArr[2] || $[4] !== propArr[3]) { + if ($[3] !== propArr[1] || $[4] !== propArr[2]) { s2 = new Set(MODULE_LOCAL.values()); s2.add(propArr[1]); s3 = new Set(s2.values()); s3.add(propArr[2]); - - s4 = new Set(s3); - s4.add(propArr[3]); - $[2] = propArr[1]; - $[3] = propArr[2]; - $[4] = propArr[3]; + $[3] = propArr[1]; + $[4] = propArr[2]; $[5] = s2; $[6] = s3; - $[7] = s4; } else { s2 = $[5]; s3 = $[6]; - s4 = $[7]; } - let t1; - if ($[8] !== s1 || $[9] !== s2 || $[10] !== s3 || $[11] !== s4) { - t1 = [s1, s2, s3, s4]; - $[8] = s1; - $[9] = s2; - $[10] = s3; - $[11] = s4; - $[12] = t1; + let s4; + if ($[7] !== propArr[3] || $[8] !== s3) { + s4 = new Set(s3); + s4.add(propArr[3]); + $[7] = propArr[3]; + $[8] = s3; + $[9] = s4; } else { - t1 = $[12]; + s4 = $[9]; } - return t1; + let t2; + if ($[10] !== s1 || $[11] !== s2 || $[12] !== s3 || $[13] !== s4) { + t2 = [s1, s2, s3, s4]; + $[10] = s1; + $[11] = s2; + $[12] = s3; + $[13] = s4; + $[14] = t2; + } else { + t2 = $[14]; + } + return t2; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.ts index 9c98fc6e32..04508ac175 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.ts @@ -1,6 +1,6 @@ const MODULE_LOCAL = new Set([4, 5, 6]); function useFoo({propArr}: {propArr: Array}) { - /* TODO: Array can be memoized separately of the Set */ + /* Array can be memoized separately of the Set */ const s1 = new Set([1, 2, 3]); s1.add(propArr[0]); @@ -12,7 +12,7 @@ function useFoo({propArr}: {propArr: Array}) { s3.add(propArr[2]); /** - * TODO: s3 should be memoized separately of s4 + * s4 should be memoized separately from s3 */ const s4 = new Set(s3); s4.add(propArr[3]); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.expect.md new file mode 100644 index 0000000000..0c8fd92816 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime'; + +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = new Set(arr).forEach(mutateAndReturn); + return ( + + {[...derived]} + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 5}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime"; + +function Component(t0) { + const $ = _c(2); + const { value } = t0; + const arr = [{ value: "foo" }, { value: "bar" }, { value }]; + useIdentity(); + const derived = new Set(arr).forEach(mutateAndReturn); + let t1; + if ($[0] !== derived) { + t1 = {[...derived]}; + $[0] = derived; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 5 }], + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: derived is not iterable ]] +[[ (exception in render) TypeError: derived is not iterable ]] +[[ (exception in render) TypeError: derived is not iterable ]] +[[ (exception in render) TypeError: derived is not iterable ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.tsx new file mode 100644 index 0000000000..cdab150b63 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.tsx @@ -0,0 +1,18 @@ +import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime'; + +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = new Set(arr).forEach(mutateAndReturn); + return ( + + {[...derived]} + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 5}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md index ea3f1d4f38..b4aec392e1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md @@ -5,7 +5,7 @@ import {useIdentity, ValidateMemoization} from 'shared-runtime'; /** - * TODO fixture for granular iterator semantics: + * Fixture for granular iterator semantics: * 1. ConditionallyMutate the iterator itself, depending on whether the iterator * is a mutable iterator. * 2. Capture effect on elements within the iterator. @@ -26,7 +26,7 @@ function Validate({x, input}) { function useFoo(input) { 'use memo'; /** - * TODO: We should be able to memoize {} separately from `x`. + * We should be able to memoize {} separately from `x`. */ const x = Array.from([{}]); useIdentity(); @@ -48,7 +48,7 @@ import { c as _c } from "react/compiler-runtime"; import { useIdentity, ValidateMemoization } from "shared-runtime"; /** - * TODO fixture for granular iterator semantics: + * Fixture for granular iterator semantics: * 1. ConditionallyMutate the iterator itself, depending on whether the iterator * is a mutable iterator. * 2. Capture effect on elements within the iterator. @@ -68,29 +68,35 @@ function Validate({ x, input }) { } function useFoo(input) { "use memo"; - const $ = _c(5); - - const x = Array.from([{}]); - useIdentity(); + const $ = _c(6); let t0; - if ($[0] !== input) { - t0 = [input]; - $[0] = input; - $[1] = t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = [{}]; + $[0] = t0; } else { - t0 = $[1]; + t0 = $[0]; } - x.push(t0); + const x = Array.from(t0); + useIdentity(); let t1; - if ($[2] !== input || $[3] !== x) { - t1 = ; - $[2] = input; - $[3] = x; - $[4] = t1; + if ($[1] !== input) { + t1 = [input]; + $[1] = input; + $[2] = t1; } else { - t1 = $[4]; + t1 = $[2]; } - return t1; + x.push(t1); + let t2; + if ($[3] !== input || $[4] !== x) { + t2 = ; + $[3] = input; + $[4] = x; + $[5] = t2; + } else { + t2 = $[5]; + } + return t2; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js index 27d861692c..3e24d0b5b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js @@ -1,7 +1,7 @@ import {useIdentity, ValidateMemoization} from 'shared-runtime'; /** - * TODO fixture for granular iterator semantics: + * Fixture for granular iterator semantics: * 1. ConditionallyMutate the iterator itself, depending on whether the iterator * is a mutable iterator. * 2. Capture effect on elements within the iterator. @@ -22,7 +22,7 @@ function Validate({x, input}) { function useFoo(input) { 'use memo'; /** - * TODO: We should be able to memoize {} separately from `x`. + * We should be able to memoize {} separately from `x`. */ const x = Array.from([{}]); useIdentity(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md index 5209fd953e..ab584c1159 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md @@ -77,40 +77,46 @@ function Validate({ x, val1, val2 }) { } function useFoo(t0) { "use memo"; - const $ = _c(8); + const $ = _c(9); const { val1, val2 } = t0; - - const x = Array.from([]); - useIdentity(); let t1; - if ($[0] !== val1) { - t1 = [val1]; - $[0] = val1; - $[1] = t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[0] = t1; } else { - t1 = $[1]; + t1 = $[0]; } - x.push(t1); + const x = Array.from(t1); + useIdentity(); let t2; - if ($[2] !== val2) { - t2 = [val2]; - $[2] = val2; - $[3] = t2; + if ($[1] !== val1) { + t2 = [val1]; + $[1] = val1; + $[2] = t2; } else { - t2 = $[3]; + t2 = $[2]; } x.push(t2); let t3; - if ($[4] !== val1 || $[5] !== val2 || $[6] !== x) { - t3 = ; - $[4] = val1; - $[5] = val2; - $[6] = x; - $[7] = t3; + if ($[3] !== val2) { + t3 = [val2]; + $[3] = val2; + $[4] = t3; } else { - t3 = $[7]; + t3 = $[4]; } - return t3; + x.push(t3); + let t4; + if ($[5] !== val1 || $[6] !== val2 || $[7] !== x) { + t4 = ; + $[5] = val1; + $[6] = val2; + $[7] = x; + $[8] = t4; + } else { + t4 = $[8]; + } + return t4; } export const FIXTURE_ENTRYPOINT = {