Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1571,8 +1571,16 @@ function lowerObjectMethod(
const loc = property.node.loc ?? GeneratedSource;
const loweredFunc = lowerFunction(builder, property);

/*
* Lower ObjectMethod to FunctionExpression so that method shorthands
* receive per-function reactive scopes (same as arrow/function expression
* properties), producing more optimal memoization.
*/
return {
kind: 'ObjectMethod',
kind: 'FunctionExpression',
name: null,
nameHint: null,
type: 'FunctionExpression',
loc,
loweredFunc,
};
Expand Down Expand Up @@ -1709,9 +1717,14 @@ function lowerExpression(
if (!loweredKey) {
continue;
}
/*
* Use type 'property' since the ObjectMethod has been lowered to a
* FunctionExpression — it will be memoized independently just like
* an arrow/function-expression property.
*/
properties.push({
kind: 'ObjectProperty',
type: 'method',
type: 'property',
place,
key: loweredKey,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,33 @@
* LICENSE file in the root directory of this source tree.
*/

import {CompilerError} from '..';
import {
GeneratedSource,
HIRFunction,
Identifier,
ReactiveScope,
makeInstructionId,
} from '../HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {HIRFunction, ReactiveScope, makeInstructionId} from '../HIR';
import DisjointSet from '../Utils/DisjointSet';

/**
* Align scopes of object method values to that of their enclosing object expressions.
* To produce a well-formed JS program in Codegen, object methods and object expressions
* must be in the same ReactiveBlock as object method definitions must be inlined.
*
* Note: ObjectMethod nodes are now lowered to FunctionExpression nodes in BuildHIR,
* so they receive per-function reactive scopes automatically. This pass is retained
* as a no-op for forward compatibility.
*/

function findScopesToMerge(fn: HIRFunction): DisjointSet<ReactiveScope> {
const objectMethodDecls: Set<Identifier> = new Set();
const mergeScopesBuilder = new DisjointSet<ReactiveScope>();

for (const [_, block] of fn.body.blocks) {
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'ObjectMethod') {
objectMethodDecls.add(lvalue.identifier);
} else if (value.kind === 'ObjectExpression') {
for (const operand of eachInstructionValueOperand(value)) {
if (objectMethodDecls.has(operand.identifier)) {
const operandScope = operand.identifier.scope;
const lvalueScope = lvalue.identifier.scope;

CompilerError.invariant(
operandScope != null && lvalueScope != null,
{
reason:
'Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.',
loc: GeneratedSource,
},
);
mergeScopesBuilder.union([operandScope, lvalueScope]);
}
}
}
}
}
return mergeScopesBuilder;
function findScopesToMerge(_fn: HIRFunction): DisjointSet<ReactiveScope> {
/*
* ObjectMethod nodes are now lowered to FunctionExpression during HIR
* construction (see BuildHIR.ts lowerObjectMethod). Each function expression
* receives its own reactive scope, so no scope merging is needed here.
*/
return new DisjointSet<ReactiveScope>();
}

export function alignObjectMethodScopes(fn: HIRFunction): void {
// Handle inner functions: we assume that Scopes are disjoint across functions
for (const [_, block] of fn.body.blocks) {
for (const {value} of block.instructions) {
if (
value.kind === 'ObjectMethod' ||
value.kind === 'FunctionExpression'
) {
if (value.kind === 'FunctionExpression') {
alignObjectMethodScopes(value.loweredFunc.func);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ import { identity } from "shared-runtime";
// inferred as a context variable.

function Component() {
const obj = { method() {} };
const obj = { method: _temp };

identity(obj);

return 4;
}
function _temp() {}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,17 @@ function Foo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = {
foo() {
return identity(1);
},
};
const x = { foo: _temp };
t0 = x.foo();
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp() {
return identity(1);
}

export const FIXTURE_ENTRYPOINT = {
fn: Foo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function Component(t0) {
if ($[0] !== cond) {
x = 2;
const obj = {
method(cond_0) {
method: function (cond_0) {
if (cond_0) {
x = 4;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function hoisting() {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const x = {
foo() {
foo: function () {
return bar();
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,17 @@ function Test() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const context = {
testFn() {
return _temp;
},
};
const context = { testFn: _temp2 };
t0 = <Stringify value={context} shouldInvokeFns={true} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp2() {
return _temp;
}
function _temp() {
return "test";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
function useHook(t0) {
const { value } = t0;
return {
getValue() {
getValue: function () {
return identity(value);
},
}.getValue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function useHook(t0) {
if ($[0] !== isCond || $[1] !== value) {
t1 = isCond
? identity({
getValue() {
getValue: function () {
return value;
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function useHook(t0) {
if ($[0] !== isCond || $[1] !== value) {
t1 = isCond
? {
getValue() {
getValue: function () {
return value;
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
function useHook(t0) {
const { value } = t0;
return {
getValue() {
getValue: function () {
return identity(value);
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,11 @@ function useHook(props) {
let t0;
if ($[0] !== props) {
const x = {
getX() {
getX: function () {
return props;
},
};
const y = {
getY() {
return "y";
},
};
const y = { getY: _temp };
t0 = setProperty(x, y);
$[0] = props;
$[1] = t0;
Expand All @@ -51,6 +47,9 @@ function useHook(props) {
}
return t0;
}
function _temp() {
return "y";
}

export const FIXTURE_ENTRYPOINT = {
fn: createHookWrapper(useHook),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function useHook(a) {
if ($[0] !== a) {
const x = { a };
const obj = {
method() {
method: function () {
mutate(x);
return x;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,23 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper, mutate, mutateAndReturn } from "shared-runtime";
function useHook(t0) {
const $ = _c(2);
const $ = _c(4);
const { value } = t0;
let obj;
if ($[0] !== value) {
const x = mutateAndReturn({ value });
obj = {
getValue() {
return value;
},
const t1 = function () {
return value;
};
let t2;
if ($[2] !== t1) {
t2 = { getValue: t1 };
$[2] = t1;
$[3] = t2;
} else {
t2 = $[3];
}
obj = t2;
mutate(x);
$[0] = value;
$[1] = obj;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function useHook(t0) {
let t2;
if ($[2] !== x) {
t2 = {
getValue() {
getValue: function () {
return x;
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function useFoo() {
let t0;
if ($[0] !== state) {
t0 = {
func() {
func: function () {
return state;
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function useHook(t0) {
if ($[0] !== value) {
const x = mutateAndReturn({ value });
obj = {
getValue() {
getValue: function () {
return x;
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,17 @@ function Component() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const obj = {
method() {
return 1;
},
};
const obj = { method: _temp };
t0 = obj.method();
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp() {
return 1;
}

export const FIXTURE_ENTRYPOINT = {
fn: Component,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
import { c as _c } from "react/compiler-runtime";
import { createHookWrapper } from "shared-runtime";
function useHook(t0) {
const $ = _c(5);
const $ = _c(7);
const { a, b } = t0;
let t1;
if ($[0] !== a) {
Expand All @@ -40,20 +40,25 @@ function useHook(t0) {
t1 = $[1];
}
let t2;
if ($[2] !== b || $[3] !== t1) {
t2 = {
x: t1,
y() {
return [b];
},
if ($[2] !== b) {
t2 = function () {
return [b];
};
$[2] = b;
$[3] = t1;
$[4] = t2;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== t1 || $[5] !== t2) {
t3 = { x: t1, y: t2 };
$[4] = t1;
$[5] = t2;
$[6] = t3;
} else {
t2 = $[4];
t3 = $[6];
}
return t2;
return t3;
}

export const FIXTURE_ENTRYPOINT = {
Expand Down
Loading
Loading