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/__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 new file mode 100644 index 0000000000..8892c8d484 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md @@ -0,0 +1,91 @@ + +## Input + +```javascript +import {useIdentity, Stringify} from 'shared-runtime'; + +/** + * TODO: Note that this `Array.from` is inferred to be mutating its first + * argument. This is because React Compiler's typing system does not yet support + * annotating a function with a set of argument match cases + distinct + * definitions (polymorphism). + * + * In this case, we should be able to infer that the `Array.from` call is + * not mutating its 0th argument. + * The 0th argument should be typed as having `effect:Mutate` only when + * (1) it might be a mutable iterable or + * (2) the 1st argument might mutate its callee + */ +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = Array.from(arr, (x, idx) => ({...x, id: idx})); + return {derived.at(-1)}; +} + +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 { useIdentity, Stringify } from "shared-runtime"; + +/** + * TODO: Note that this `Array.from` is inferred to be mutating its first + * argument. This is because React Compiler's typing system does not yet support + * annotating a function with a set of argument match cases + distinct + * definitions (polymorphism). + * + * In this case, we should be able to infer that the `Array.from` call is + * not mutating its 0th argument. + * The 0th argument should be typed as having `effect:Mutate` only when + * (1) it might be a mutable iterable or + * (2) the 1st argument might mutate its callee + */ +function Component(t0) { + const $ = _c(4); + 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; + } else { + t2 = $[3]; + } + return t2; +} +function _temp(x, idx) { + return { ...x, id: idx }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 5 }], + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }], +}; + +``` + +### Eval output +(kind: ok)
{"children":{"value":5,"id":2}}
+
{"children":{"value":6,"id":2}}
+
{"children":{"value":6,"id":2}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js new file mode 100644 index 0000000000..f2b364bc60 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.js @@ -0,0 +1,26 @@ +import {useIdentity, Stringify} from 'shared-runtime'; + +/** + * TODO: Note that this `Array.from` is inferred to be mutating its first + * argument. This is because React Compiler's typing system does not yet support + * annotating a function with a set of argument match cases + distinct + * definitions (polymorphism). + * + * In this case, we should be able to infer that the `Array.from` call is + * not mutating its 0th argument. + * The 0th argument should be typed as having `effect:Mutate` only when + * (1) it might be a mutable iterable or + * (2) the 1st argument might mutate its callee + */ +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = Array.from(arr, (x, idx) => ({...x, id: idx})); + return {derived.at(-1)}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 5}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}], +}; 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 new file mode 100644 index 0000000000..66d0b42584 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md @@ -0,0 +1,88 @@ + +## Input + +```javascript +import {useIdentity, Stringify} from 'shared-runtime'; + +/** + * TODO: Note that this `Array.from` is inferred to be mutating its first + * argument. This is because React Compiler's typing system does not yet support + * annotating a function with a set of argument match cases + distinct + * definitions (polymorphism) + * + * In this case, we should be able to infer that the `Array.from` call is + * not mutating its 0th argument. + * The 0th argument should be typed as having `effect:Mutate` only when + * (1) it might be a mutable iterable or + * (2) the 1st argument might mutate its callee + */ +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = Array.from(arr); + return {derived.at(-1)}; +} + +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 { useIdentity, Stringify } from "shared-runtime"; + +/** + * TODO: Note that this `Array.from` is inferred to be mutating its first + * argument. This is because React Compiler's typing system does not yet support + * annotating a function with a set of argument match cases + distinct + * definitions (polymorphism) + * + * In this case, we should be able to infer that the `Array.from` call is + * not mutating its 0th argument. + * The 0th argument should be typed as having `effect:Mutate` only when + * (1) it might be a mutable iterable or + * (2) the 1st argument might mutate its callee + */ +function Component(t0) { + const $ = _c(4); + 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; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 5 }], + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }], +}; + +``` + +### Eval output +(kind: ok)
{"children":{"value":5}}
+
{"children":{"value":6}}
+
{"children":{"value":6}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js new file mode 100644 index 0000000000..c9b09c384d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.js @@ -0,0 +1,26 @@ +import {useIdentity, Stringify} from 'shared-runtime'; + +/** + * TODO: Note that this `Array.from` is inferred to be mutating its first + * argument. This is because React Compiler's typing system does not yet support + * annotating a function with a set of argument match cases + distinct + * definitions (polymorphism) + * + * In this case, we should be able to infer that the `Array.from` call is + * not mutating its 0th argument. + * The 0th argument should be typed as having `effect:Mutate` only when + * (1) it might be a mutable iterable or + * (2) the 1st argument might mutate its callee + */ +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = Array.from(arr); + return {derived.at(-1)}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 5}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}], +}; 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 new file mode 100644 index 0000000000..586124280a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime'; + +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = Array.from(arr, mutateAndReturn); + return {derived.at(-1)}; +} + +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"; + +function Component(t0) { + const $ = _c(4); + 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); + $[0] = derived; + $[1] = t1; + } else { + t1 = $[1]; + } + let t2; + if ($[2] !== t1) { + t2 = {t1}; + $[2] = t1; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: 5 }], + sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }], +}; + +``` + +### Eval output +(kind: ok)
{"children":{"value":5,"wat0":"joe"}}
+
{"children":{"value":6,"wat0":"joe"}}
+
{"children":{"value":6,"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 new file mode 100644 index 0000000000..edb4e37125 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js @@ -0,0 +1,14 @@ +import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime'; + +function Component({value}) { + const arr = [{value: 'foo'}, {value: 'bar'}, {value}]; + useIdentity(); + const derived = Array.from(arr, mutateAndReturn); + return {derived.at(-1)}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{value: 5}], + sequentialRenders: [{value: 5}, {value: 6}, {value: 6}], +}; 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 1ba01dc5bf..ea3f1d4f38 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 @@ -68,21 +68,29 @@ function Validate({ x, input }) { } function useFoo(input) { "use memo"; - const $ = _c(3); + const $ = _c(5); const x = Array.from([{}]); useIdentity(); - x.push([input]); let t0; - if ($[0] !== input || $[1] !== x) { - t0 = ; + if ($[0] !== input) { + t0 = [input]; $[0] = input; - $[1] = x; - $[2] = t0; + $[1] = t0; } else { - t0 = $[2]; + t0 = $[1]; } - return t0; + 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 = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md similarity index 71% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md index 6061464afc..5209fd953e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md @@ -37,6 +37,12 @@ function useFoo({val1, val2}) { 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}, + ], }; ``` @@ -71,29 +77,51 @@ function Validate({ x, val1, val2 }) { } function useFoo(t0) { "use memo"; - const $ = _c(4); + const $ = _c(8); const { val1, val2 } = t0; const x = Array.from([]); useIdentity(); - x.push([val1]); - x.push([val2]); let t1; - if ($[0] !== val1 || $[1] !== val2 || $[2] !== x) { - t1 = ; + if ($[0] !== val1) { + t1 = [val1]; $[0] = val1; - $[1] = val2; - $[2] = x; - $[3] = t1; + $[1] = t1; } else { - t1 = $[3]; + t1 = $[1]; } - return t1; + 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 }, + ], }; ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js similarity index 87% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js index d1a80a4ea7..dfd4e0e0f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.js @@ -33,4 +33,10 @@ function useFoo({val1, val2}) { 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}, + ], };