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 3fe2e938ce..df8196c1d7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -119,8 +119,8 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ ], /* * https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.from - * Array.from(arrayLike, optionalFn, optionalThis) not added because - * the Effect of `arrayLike` is polymorphic i.e. + * Array.from(arrayLike, optionalFn, optionalThis) + * Note that the Effect of `arrayLike` is polymorphic i.e. * - Effect.read if * - it does not have an @iterator property and is array-like * (i.e. has a length property) @@ -128,6 +128,20 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [ * - Effect.mutate if it is a self-mutative iterator (e.g. a generator * function) */ + [ + 'from', + addFunction(DEFAULT_SHAPES, [], { + positionalParams: [ + Effect.ConditionallyMutate, + Effect.ConditionallyMutate, + Effect.ConditionallyMutate, + ], + restParam: Effect.Read, + returnType: {kind: 'Object', shapeId: BuiltInArrayId}, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Mutable, + }), + ], [ 'of', // Array.of(element0, ..., elementN) 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 3a12285d47..060f1cbc70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts @@ -879,11 +879,31 @@ function inferBlock( reason: new Set([ValueReason.Other]), context: new Set(), }; + + for (const element of instrValue.elements) { + if (element.kind === 'Spread') { + state.referenceAndRecordEffects( + freezeActions, + element.place, + Effect.ConditionallyMutate, + ValueReason.Other, + ); + } else if (element.kind === 'Identifier') { + state.referenceAndRecordEffects( + freezeActions, + element, + Effect.Capture, + ValueReason.Other, + ); + } else { + let _: 'Hole' = element.kind; + } + } + state.initialize(instrValue, valueKind); + state.define(instr.lvalue, instrValue); + instr.lvalue.effect = Effect.Store; continuation = { - kind: 'initialize', - valueKind, - effect: {kind: Effect.Capture, reason: ValueReason.Other}, - lvalueEffect: Effect.Store, + kind: 'funeffects', }; break; } @@ -1248,21 +1268,12 @@ function inferBlock( for (let i = 0; i < instrValue.args.length; i++) { const arg = instrValue.args[i]; const place = arg.kind === 'Identifier' ? arg : arg.place; - if (effects !== null) { - state.referenceAndRecordEffects( - freezeActions, - place, - effects[i], - ValueReason.Other, - ); - } else { - state.referenceAndRecordEffects( - freezeActions, - place, - Effect.ConditionallyMutate, - ValueReason.Other, - ); - } + state.referenceAndRecordEffects( + freezeActions, + place, + getArgumentEffect(effects != null ? effects[i] : null, arg), + ValueReason.Other, + ); hasCaptureArgument ||= place.effect === Effect.Capture; } if (signature !== null) { @@ -1314,7 +1325,10 @@ function inferBlock( signature !== null ? { kind: signature.returnValueKind, - reason: new Set([ValueReason.Other]), + reason: new Set([ + signature.returnValueReason ?? + ValueReason.KnownReturnSignature, + ]), context: new Set(), } : { @@ -1337,7 +1351,8 @@ function inferBlock( state.referenceAndRecordEffects( freezeActions, place, - Effect.Read, + // see call-spread-argument-mutable-iterator test fixture + arg.kind === 'Spread' ? Effect.ConditionallyMutate : Effect.Read, ValueReason.Other, ); } @@ -1363,25 +1378,16 @@ function inferBlock( for (let i = 0; i < instrValue.args.length; i++) { const arg = instrValue.args[i]; const place = arg.kind === 'Identifier' ? arg : arg.place; - if (effects !== null) { - /* - * If effects are inferred for an argument, we should fail invalid - * mutating effects - */ - state.referenceAndRecordEffects( - freezeActions, - place, - effects[i], - ValueReason.Other, - ); - } else { - state.referenceAndRecordEffects( - freezeActions, - place, - Effect.ConditionallyMutate, - ValueReason.Other, - ); - } + /* + * If effects are inferred for an argument, we should fail invalid + * mutating effects + */ + state.referenceAndRecordEffects( + freezeActions, + place, + getArgumentEffect(effects != null ? effects[i] : null, arg), + ValueReason.Other, + ); hasCaptureArgument ||= place.effect === Effect.Capture; } if (signature !== null) { @@ -2056,3 +2062,31 @@ function areArgumentsImmutableAndNonMutating( } return true; } + +function getArgumentEffect( + signatureEffect: Effect | null, + arg: Place | SpreadPattern, +): Effect { + if (signatureEffect != null) { + if (arg.kind === 'Identifier') { + return signatureEffect; + } else if ( + signatureEffect === Effect.Mutate || + signatureEffect === Effect.ConditionallyMutate + ) { + return signatureEffect; + } else { + // see call-spread-argument-mutable-iterator test fixture + if (signatureEffect === Effect.Freeze) { + CompilerError.throwTodo({ + reason: 'Support spread syntax for hook arguments', + loc: arg.place.loc, + }); + } + // effects[i] is Effect.Capture | Effect.Read | Effect.Store + return Effect.ConditionallyMutate; + } + } else { + return Effect.ConditionallyMutate; + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-later-mutated.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-later-mutated.expect.md new file mode 100644 index 0000000000..a317e22faf --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-later-mutated.expect.md @@ -0,0 +1,62 @@ + +## Input + +```javascript +function useBar({arg}) { + /** + * Note that mutableIterator is mutated by the later object spread. Therefore, + * `s.values()` should be memoized within the same block as the object spread. + * In terms of compiler internals, they should have the same reactive scope. + */ + const obj = {}; + const s = new Set([obj, 5, 4]); + const mutableIterator = s.values(); + const arr = [...mutableIterator]; + + obj.x = arg; + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useBar, + params: [{arg: 3}], + sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function useBar(t0) { + const $ = _c(2); + const { arg } = t0; + let arr; + if ($[0] !== arg) { + const obj = {}; + const s = new Set([obj, 5, 4]); + const mutableIterator = s.values(); + arr = [...mutableIterator]; + + obj.x = arg; + $[0] = arg; + $[1] = arr; + } else { + arr = $[1]; + } + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useBar, + params: [{ arg: 3 }], + sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }], +}; + +``` + +### Eval output +(kind: ok) [{"x":3},5,4] +[{"x":3},5,4] +[{"x":4},5,4] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-later-mutated.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-later-mutated.js new file mode 100644 index 0000000000..036ce2ddfc --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-later-mutated.js @@ -0,0 +1,20 @@ +function useBar({arg}) { + /** + * Note that mutableIterator is mutated by the later object spread. Therefore, + * `s.values()` should be memoized within the same block as the object spread. + * In terms of compiler internals, they should have the same reactive scope. + */ + const obj = {}; + const s = new Set([obj, 5, 4]); + const mutableIterator = s.values(); + const arr = [...mutableIterator]; + + obj.x = arg; + return arr; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useBar, + params: [{arg: 3}], + sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-mutable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-mutable-iterator.expect.md new file mode 100644 index 0000000000..25499af1b0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-mutable-iterator.expect.md @@ -0,0 +1,85 @@ + +## Input + +```javascript +/** + * TODO: object spreads should have conditionally mutate semantics + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [3,1,5,4] + * [3,1,5,4] + * [4,1,5,4] + * Forget: + * (kind: ok) [3,1,5,4] + * [3,1,5,4] + * [4] + */ + +function useBar({arg}) { + 'use memo'; + + /** + * Note that mutableIterator is mutated by the later object spread. Therefore, + * `s.values()` should be memoized within the same block as the object spread. + * In terms of compiler internals, they should have the same reactive scope. + */ + const s = new Set([1, 5, 4]); + const mutableIterator = s.values(); + + return [arg, ...mutableIterator]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useBar, + params: [{arg: 3}], + sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; /** + * TODO: object spreads should have conditionally mutate semantics + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [3,1,5,4] + * [3,1,5,4] + * [4,1,5,4] + * Forget: + * (kind: ok) [3,1,5,4] + * [3,1,5,4] + * [4] + */ + +function useBar(t0) { + "use memo"; + const $ = _c(2); + const { arg } = t0; + let t1; + if ($[0] !== arg) { + const s = new Set([1, 5, 4]); + const mutableIterator = s.values(); + + t1 = [arg, ...mutableIterator]; + $[0] = arg; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useBar, + params: [{ arg: 3 }], + sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }], +}; + +``` + +### Eval output +(kind: ok) [3,1,5,4] +[3,1,5,4] +[4,1,5,4] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-mutable-iterator.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-mutable-iterator.js new file mode 100644 index 0000000000..c83a9e53e6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-spread-mutable-iterator.js @@ -0,0 +1,32 @@ +/** + * TODO: object spreads should have conditionally mutate semantics + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok) [3,1,5,4] + * [3,1,5,4] + * [4,1,5,4] + * Forget: + * (kind: ok) [3,1,5,4] + * [3,1,5,4] + * [4] + */ + +function useBar({arg}) { + 'use memo'; + + /** + * Note that mutableIterator is mutated by the later object spread. Therefore, + * `s.values()` should be memoized within the same block as the object spread. + * In terms of compiler internals, they should have the same reactive scope. + */ + const s = new Set([1, 5, 4]); + const mutableIterator = s.values(); + + return [arg, ...mutableIterator]; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useBar, + params: [{arg: 3}], + sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/call-spread-argument-mutable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/call-spread-argument-mutable-iterator.expect.md new file mode 100644 index 0000000000..74fb57b6bc --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/call-spread-argument-mutable-iterator.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +import {useIdentity} from 'shared-runtime'; + +function useFoo() { + const it = new Set([1, 2]).values(); + useIdentity(); + return Math.max(...it); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +## Code + +```javascript +import { useIdentity } from "shared-runtime"; + +function useFoo() { + const it = new Set([1, 2]).values(); + useIdentity(); + return Math.max(...it); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{}], + sequentialRenders: [{}, {}], +}; + +``` + +### Eval output +(kind: ok) 2 +2 \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/call-spread-argument-mutable-iterator.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/call-spread-argument-mutable-iterator.js new file mode 100644 index 0000000000..1b30f0a46f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/call-spread-argument-mutable-iterator.js @@ -0,0 +1,13 @@ +import {useIdentity} from 'shared-runtime'; + +function useFoo() { + const it = new Set([1, 2]).values(); + useIdentity(); + return Math.max(...it); +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{}], + sequentialRenders: [{}, {}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.expect.md index 877c15e883..56bf9e3d62 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.expect.md @@ -4,9 +4,10 @@ ```javascript import {makeArray} from 'shared-runtime'; -function Component(props) { +const other = [0, 1]; +function Component({}) { const items = makeArray(0, 1, 2, null, 4, false, 6); - const max = Math.max(...items.filter(Boolean)); + const max = Math.max(2, items.push(5), ...other); return max; } @@ -21,13 +22,13 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` - 3 | function Component(props) { - 4 | const items = makeArray(0, 1, 2, null, 4, false, 6); -> 5 | const max = Math.max(...items.filter(Boolean)); - | ^^^^^^^^ Invariant: [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier` (5:5) - 6 | return max; - 7 | } - 8 | + 4 | function Component({}) { + 5 | const items = makeArray(0, 1, 2, null, 4, false, 6); +> 6 | const max = Math.max(2, items.push(5), ...other); + | ^^^^^^^^ Invariant: [Codegen] Internal error: MethodCall::property must be an unpromoted + unmemoized MemberExpression. Got a `Identifier` (6:6) + 7 | return max; + 8 | } + 9 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.js index 475cf383cf..b2883c3303 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-nested-method-calls-lower-property-load-into-temporary.js @@ -1,8 +1,9 @@ import {makeArray} from 'shared-runtime'; -function Component(props) { +const other = [0, 1]; +function Component({}) { const items = makeArray(0, 1, 2, null, 4, false, 6); - const max = Math.max(...items.filter(Boolean)); + const max = Math.max(2, items.push(5), ...other); return max; } 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 new file mode 100644 index 0000000000..ea3f1d4f38 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md @@ -0,0 +1,104 @@ + +## Input + +```javascript +import {useIdentity, ValidateMemoization} from 'shared-runtime'; + +/** + * TODO 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. + */ +function Validate({x, input}) { + 'use no memo'; + return ( + <> + + + + ); +} +function useFoo(input) { + 'use memo'; + /** + * TODO: We should be able to memoize {} separately from `x`. + */ + const x = Array.from([{}]); + useIdentity(); + x.push([input]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [1], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useIdentity, ValidateMemoization } from "shared-runtime"; + +/** + * TODO 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. + */ +function Validate({ x, input }) { + "use no memo"; + return ( + <> + + + + ); +} +function useFoo(input) { + "use memo"; + const $ = _c(5); + + const x = Array.from([{}]); + useIdentity(); + let t0; + if ($[0] !== input) { + t0 = [input]; + $[0] = input; + $[1] = t0; + } else { + t0 = $[1]; + } + x.push(t0); + let t1; + if ($[2] !== input || $[3] !== x) { + t1 = ; + $[2] = input; + $[3] = x; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [1], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[],"output":{}}
{"inputs":[1],"output":[1]}
\ No newline at end of file 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 new file mode 100644 index 0000000000..27d861692c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js @@ -0,0 +1,36 @@ +import {useIdentity, ValidateMemoization} from 'shared-runtime'; + +/** + * TODO 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. + */ +function Validate({x, input}) { + 'use no memo'; + return ( + <> + + + + ); +} +function useFoo(input) { + 'use memo'; + /** + * TODO: We should be able to memoize {} separately from `x`. + */ + const x = Array.from([{}]); + useIdentity(); + x.push([input]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [1], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.expect.md new file mode 100644 index 0000000000..29d2a53973 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.expect.md @@ -0,0 +1,48 @@ + +## Input + +```javascript +import {makeArray} from 'shared-runtime'; + +function Component(props) { + const items = makeArray(0, 1, 2, null, 4, false, 6); + const max = Math.max(...items.filter(Boolean)); + return max; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { makeArray } from "shared-runtime"; + +function Component(props) { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const items = makeArray(0, 1, 2, null, 4, false, 6); + t0 = Math.max(...items.filter(Boolean)); + $[0] = t0; + } else { + t0 = $[0]; + } + const max = t0; + return max; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; + +``` + +### Eval output +(kind: ok) 6 \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.js new file mode 100644 index 0000000000..b2883c3303 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.js @@ -0,0 +1,13 @@ +import {makeArray} from 'shared-runtime'; + +const other = [0, 1]; +function Component({}) { + const items = makeArray(0, 1, 2, null, 4, false, 6); + const max = Math.max(2, items.push(5), ...other); + return max; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; 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 new file mode 100644 index 0000000000..5209fd953e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md @@ -0,0 +1,130 @@ + +## Input + +```javascript +import {useIdentity, ValidateMemoization} from 'shared-runtime'; + +/** + * Fixture to assert that we can infer the type and effects of an array created + * with `Array.from`. + */ +function Validate({x, val1, val2}) { + 'use no memo'; + return ( + <> + + + + ); +} +function useFoo({val1, val2}) { + 'use memo'; + const x = Array.from([]); + useIdentity(); + x.push([val1]); + x.push([val2]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{val1: 1, val2: 2}], + params: [ + {val1: 1, val2: 2}, + {val1: 1, val2: 2}, + {val1: 1, val2: 3}, + {val1: 4, val2: 2}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useIdentity, ValidateMemoization } from "shared-runtime"; + +/** + * Fixture to assert that we can infer the type and effects of an array created + * with `Array.from`. + */ +function Validate({ x, val1, val2 }) { + "use no memo"; + return ( + <> + + + + + ); +} +function useFoo(t0) { + "use memo"; + const $ = _c(8); + const { val1, val2 } = t0; + + const x = Array.from([]); + useIdentity(); + let t1; + if ($[0] !== val1) { + t1 = [val1]; + $[0] = val1; + $[1] = t1; + } else { + t1 = $[1]; + } + x.push(t1); + let t2; + if ($[2] !== val2) { + t2 = [val2]; + $[2] = val2; + $[3] = t2; + } else { + t2 = $[3]; + } + x.push(t2); + let t3; + if ($[4] !== val1 || $[5] !== val2 || $[6] !== x) { + t3 = ; + $[4] = val1; + $[5] = val2; + $[6] = x; + $[7] = t3; + } else { + t3 = $[7]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{ val1: 1, val2: 2 }], + params: [ + { val1: 1, val2: 2 }, + { val1: 1, val2: 2 }, + { val1: 1, val2: 3 }, + { val1: 4, val2: 2 }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[1],"output":[1]}
{"inputs":[2],"output":[2]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js new file mode 100644 index 0000000000..dfd4e0e0f1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js @@ -0,0 +1,42 @@ +import {useIdentity, ValidateMemoization} from 'shared-runtime'; + +/** + * Fixture to assert that we can infer the type and effects of an array created + * with `Array.from`. + */ +function Validate({x, val1, val2}) { + 'use no memo'; + return ( + <> + + + + ); +} +function useFoo({val1, val2}) { + 'use memo'; + const x = Array.from([]); + useIdentity(); + x.push([val1]); + x.push([val2]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: useFoo, + params: [{val1: 1, val2: 2}], + params: [ + {val1: 1, val2: 2}, + {val1: 1, val2: 2}, + {val1: 1, val2: 3}, + {val1: 4, val2: 2}, + ], +};