diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 90921454c864f..8a97eea217b33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -104,6 +104,7 @@ import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetSta import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement'; import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR'; import {outlineJSX} from '../Optimization/OutlineJsx'; +import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls'; export type CompilerPipelineValue = | {kind: 'ast'; name: string; value: CodegenFunction} @@ -209,6 +210,9 @@ function* runWithEnvironment( lowerContextAccess(hir, env.config.lowerContextAccess); } + optimizePropsMethodCalls(hir); + yield log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir}); + analyseFunctions(hir); yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir}); 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 954fb6f40053a..475d8e8bec574 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1644,6 +1644,10 @@ export function isArrayType(id: Identifier): boolean { return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray'; } +export function isPropsType(id: Identifier): boolean { + return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps'; +} + export function isRefValueType(id: Identifier): boolean { return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls.ts new file mode 100644 index 0000000000000..ab686ca219315 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/OptimizePropsMethodCalls.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {HIRFunction, isPropsType} from '../HIR'; + +/** + * Converts method calls into regular calls where the receiver is the props object: + * + * Example: + * + * ``` + * // INPUT + * props.foo(); + * + * // OUTPUT + * const t0 = props.foo; + * t0(); + * ``` + * + * Counter example: + * + * Here the receiver is `props.foo`, not the props object, so we don't rewrite it: + * + * // INPUT + * props.foo.bar(); + * + * // OUTPUT + * props.foo.bar(); + * ``` + */ +export function optimizePropsMethodCalls(fn: HIRFunction): void { + for (const [, block] of fn.body.blocks) { + for (let i = 0; i < block.instructions.length; i++) { + const instr = block.instructions[i]!; + if ( + instr.value.kind === 'MethodCall' && + isPropsType(instr.value.receiver.identifier) + ) { + instr.value = { + kind: 'CallExpression', + callee: instr.value.property, + args: instr.value.args, + loc: instr.value.loc, + }; + } + } + } +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md index b84229156bc14..962778c5f7601 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-outlining-child-stored-in-id.expect.md @@ -50,11 +50,11 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; // @enableJsxOutlining function Component(arr) { - const $ = _c(3); + const $ = _c(6); const x = useX(); let t0; if ($[0] !== arr || $[1] !== x) { - t0 = arr.map((i) => { + t0 = (i) => { arr.map((i_0, id) => { const T0 = _temp; const child = ; @@ -62,14 +62,23 @@ function Component(arr) { const jsx =
{child}
; return jsx; }); - }); + }; $[0] = arr; $[1] = x; $[2] = t0; } else { t0 = $[2]; } - return t0; + let t1; + if ($[3] !== arr.map || $[4] !== t0) { + t1 = arr.map(t0); + $[3] = arr.map; + $[4] = t0; + $[5] = t1; + } else { + t1 = $[5]; + } + return t1; } function _temp(t0) { const $ = _c(5); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md new file mode 100644 index 0000000000000..0fd80ab72989b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md @@ -0,0 +1,41 @@ + +## Input + +```javascript +function Component(props) { + const x = props.x(); + return
{x}
; +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component(props) { + const $ = _c(4); + let t0; + if ($[0] !== props.x) { + t0 = props.x(); + $[0] = props.x; + $[1] = t0; + } else { + t0 = $[1]; + } + const x = t0; + let t1; + if ($[2] !== x) { + t1 =
{x}
; + $[2] = x; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js new file mode 100644 index 0000000000000..5dadee5f743aa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js @@ -0,0 +1,4 @@ +function Component(props) { + const x = props.x(); + return
{x}
; +}