From b1f7ebfd47d3ecc1c9561a43c3c68d129b240484 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Mon, 11 Sep 2023 12:49:47 -0700 Subject: [PATCH] Reodering of unary and arrow function expressions Supports reordering of unary and arrow function expressions: * Supports a trivially safe subset of unary operators, rejects things like `void` just because we don't need it yet. * Supports arrow function expressions that are either an empty block statement or a single expression which is itself reorderable. --- .../src/HIR/BuildHIR.ts | 25 ++++++++ .../default-param-array-with-unary.expect.md | 41 +++++++++++++ .../default-param-array-with-unary.js | 8 +++ ...efault-param-with-empty-callback.expect.md | 41 +++++++++++++ .../default-param-with-empty-callback.js | 8 +++ ...-param-with-reorderable-callback.expect.md | 41 +++++++++++++ ...default-param-with-reorderable-callback.js | 8 +++ ...ructure-default-array-with-unary.expect.md | 43 ++++++++++++++ .../destructure-default-array-with-unary.js | 9 +++ ...ction-with-param-as-captured-dep.expect.md | 29 ---------- ...ction-with-param-as-captured-dep.expect.md | 57 +++++++++++++++++++ ...ed-function-with-param-as-captured-dep.ts} | 2 +- .../packages/sprout/src/SproutTodoFilter.ts | 1 + 13 files changed, 283 insertions(+), 30 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.js create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.js delete mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.expect.md rename compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/{error.nested-function-with-param-as-captured-dep.ts => nested-function-with-param-as-captured-dep.ts} (88%) diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts index ca0d26ce61..24e791d905 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts @@ -8,6 +8,7 @@ import { NodePath, Scope } from "@babel/traverse"; import * as t from "@babel/types"; import { Expression } from "@babel/types"; +import invariant from "invariant"; import { CompilerError, CompilerSuggestionOperation, @@ -2238,6 +2239,19 @@ function isReorderableExpression( case "BigIntLiteral": { return true; } + case "UnaryExpression": { + const unary = expr as NodePath; + switch (expr.node.operator) { + case "!": + case "+": + case "-": { + return isReorderableExpression(builder, unary.get("argument")); + } + default: { + return false; + } + } + } case "TypeCastExpression": { return isReorderableExpression( builder, @@ -2293,6 +2307,17 @@ function isReorderableExpression( return false; } } + case "ArrowFunctionExpression": { + const fn = expr as NodePath; + const body = fn.get("body"); + if (body.node.type === "BlockStatement") { + return body.node.body.length === 0; + } else { + // For TypeScript + invariant(body.isExpression(), "Expected an expression"); + return isReorderableExpression(builder, body); + } + } default: { return false; } diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.expect.md new file mode 100644 index 0000000000..3db2d45363 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.expect.md @@ -0,0 +1,41 @@ + +## Input + +```javascript +function Component(x = [-1, 1]) { + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(t0) { + const $ = useMemoCache(2); + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? [-1, 1] : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const x = t1; + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.js new file mode 100644 index 0000000000..bf0dd4b5ff --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-array-with-unary.js @@ -0,0 +1,8 @@ +function Component(x = [-1, 1]) { + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.expect.md new file mode 100644 index 0000000000..60c012bed9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.expect.md @@ -0,0 +1,41 @@ + +## Input + +```javascript +function Component(x = () => {}) { + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(t0) { + const $ = useMemoCache(2); + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? () => {} : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const x = t1; + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.js new file mode 100644 index 0000000000..0340353408 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-empty-callback.js @@ -0,0 +1,8 @@ +function Component(x = () => {}) { + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.expect.md new file mode 100644 index 0000000000..94705daf04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.expect.md @@ -0,0 +1,41 @@ + +## Input + +```javascript +function Component(x = () => [-1, true, 42.0, "hello"]) { + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(t0) { + const $ = useMemoCache(2); + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? () => [-1, true, 42, "hello"] : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const x = t1; + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.js new file mode 100644 index 0000000000..3b7630fba7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/default-param-with-reorderable-callback.js @@ -0,0 +1,8 @@ +function Component(x = () => [-1, true, 42.0, "hello"]) { + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.expect.md new file mode 100644 index 0000000000..cabb14eea1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +function Component(props) { + const [x = [-1, 1]] = props.value; + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: [] }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(2); + const [t0] = props.value; + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? [-1, 1] : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const x = t1; + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: [] }], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.js new file mode 100644 index 0000000000..90dfe07ce0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-default-array-with-unary.js @@ -0,0 +1,9 @@ +function Component(props) { + const [x = [-1, 1]] = props.value; + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ value: [] }], +}; diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.expect.md deleted file mode 100644 index 0390e8e9eb..0000000000 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.expect.md +++ /dev/null @@ -1,29 +0,0 @@ - -## Input - -```javascript -function Foo() { - (function t() { - let x = {}; - return function a(x = () => {}) { - return x; - }; - })(); -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [], - isComponent: false, -}; - -``` - - -## Error - -``` -[ReactForget] Todo: (BuildHIR::node.lowerReorderableExpression) Expression type 'ArrowFunctionExpression' cannot be safely reordered (4:4) -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.expect.md new file mode 100644 index 0000000000..548bbd0857 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.expect.md @@ -0,0 +1,57 @@ + +## Input + +```javascript +function Foo() { + return (function t() { + let x = {}; + return function a(x = () => {}) { + return x; + }; + })(); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Foo() { + const $ = useMemoCache(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = function t() { + return function a(t25) { + const x_0 = t25 === undefined ? () => {} : t25; + return x_0; + }; + }; + $[0] = t0; + } else { + t0 = $[0]; + } + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = t0(); + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], + isComponent: false, +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.ts b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.ts similarity index 88% rename from compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.ts rename to compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.ts index c01ab3a084..746df1cb40 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.nested-function-with-param-as-captured-dep.ts +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/nested-function-with-param-as-captured-dep.ts @@ -1,5 +1,5 @@ function Foo() { - (function t() { + return (function t() { let x = {}; return function a(x = () => {}) { return x; diff --git a/compiler/packages/sprout/src/SproutTodoFilter.ts b/compiler/packages/sprout/src/SproutTodoFilter.ts index 1f4d4fb2d4..6c6420e71c 100644 --- a/compiler/packages/sprout/src/SproutTodoFilter.ts +++ b/compiler/packages/sprout/src/SproutTodoFilter.ts @@ -412,6 +412,7 @@ const skipFilter = new Set([ "fbt-call-complex-param-value", "fbt-template-string-same-scope", "component-declaration-basic.flow", + "nested-function-with-param-as-captured-dep", // TODO: 🌲 "forest-basic",