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
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,9 @@ function findFunctionsToCompile(
programContext: ProgramContext,
): Array<CompileSource> {
const queue: Array<CompileSource> = [];
// Track functions that have 'use no memo' to skip their nested functions
const optedOutFunctions: Set<t.Node> = new Set();

const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
// In 'all' mode, compile only top level functions
if (
Expand All @@ -529,6 +532,18 @@ function findFunctionsToCompile(
return;
}

// Check if this function has 'use no memo' directive and track it
if (fn.node.body.type === 'BlockStatement') {
const optOut = findDirectiveDisablingMemoization(
fn.node.body.directives,
pass.opts,
);
if (optOut != null) {
// Track this function so nested functions are also skipped
optedOutFunctions.add(fn.node);
}
}

const fnType = getReactFunctionType(fn, pass);

if (pass.opts.environment.validateNoDynamicallyCreatedComponentsOrHooks) {
Expand All @@ -539,6 +554,34 @@ function findFunctionsToCompile(
return;
}

// Check if this function explicitly opts in with 'use memo'
let hasExplicitOptIn = false;
if (fn.node.body.type === 'BlockStatement') {
const optIn = tryFindDirectiveEnablingMemoization(
fn.node.body.directives,
pass.opts,
);
hasExplicitOptIn = optIn.isOk() && optIn.unwrap() != null;
}

// Skip if any ancestor function has 'use no memo', unless this function explicitly opts in
if (!hasExplicitOptIn) {
let parentPath: NodePath<t.Node> | null = fn.parentPath;
while (parentPath != null) {
if (
parentPath.isFunctionDeclaration() ||
parentPath.isFunctionExpression() ||
parentPath.isArrowFunctionExpression()
) {
if (optedOutFunctions.has(parentPath.node)) {
// Parent has 'use no memo', skip this nested function
return;
}
}
parentPath = parentPath.parentPath;
}
}

/*
* We may be generating a new FunctionDeclaration node, so we must skip over it or this
* traversal will loop infinitely.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

## Input

```javascript
// @compilationMode(infer)

/**
* Test that deeply nested functions are NOT compiled when top-level parent has 'use no memo'.
*/
function ParentComponent(props) {
'use no memo';

function Level1() {
function Level2() {
return <div>{props.value}</div>;
}
return Level2;
}

return props.render(Level1());
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [{value: 'test', render: C => C()}],
isComponent: true,
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)

/**
* Test that deeply nested functions are NOT compiled when top-level parent has 'use no memo'.
*/
function ParentComponent(props) {
"use no memo";

function Level1() {
function Level2() {
return <div>{props.value}</div>;
}
return Level2;
}

return props.render(Level1());
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [
{
value: "test",
render: (C) => {
const $ = _c(2);
let t0;
if ($[0] !== C) {
t0 = C();
$[0] = C;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
},
},
],
isComponent: true,
};

```

### Eval output
(kind: ok) <div>test</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @compilationMode(infer)

/**
* Test that deeply nested functions are NOT compiled when top-level parent has 'use no memo'.
*/
function ParentComponent(props) {
'use no memo';

function Level1() {
function Level2() {
return <div>{props.value}</div>;
}
return Level2;
}

return props.render(Level1());
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [{value: 'test', render: C => C()}],
isComponent: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

## Input

```javascript
// @compilationMode(infer)

/**
* Test that nested arrow functions with component-like names are NOT compiled
* when parent has 'use no memo'.
*/
function ParentComponent(props) {
'use no memo';

const NestedComponent = () => {
return <div>{props.value}</div>;
};

return props.render(NestedComponent);
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [{value: 'test', render: C => C()}],
isComponent: true,
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)

/**
* Test that nested arrow functions with component-like names are NOT compiled
* when parent has 'use no memo'.
*/
function ParentComponent(props) {
"use no memo";

const NestedComponent = () => {
return <div>{props.value}</div>;
};

return props.render(NestedComponent);
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [
{
value: "test",
render: (C) => {
const $ = _c(2);
let t0;
if ($[0] !== C) {
t0 = C();
$[0] = C;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
},
},
],
isComponent: true,
};

```

### Eval output
(kind: ok) <div>test</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @compilationMode(infer)

/**
* Test that nested arrow functions with component-like names are NOT compiled
* when parent has 'use no memo'.
*/
function ParentComponent(props) {
'use no memo';

const NestedComponent = () => {
return <div>{props.value}</div>;
};

return props.render(NestedComponent);
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [{value: 'test', render: C => C()}],
isComponent: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

## Input

```javascript
// @compilationMode(infer)

/**
* Test that nested component-like functions are NOT compiled when parent has 'use no memo'.
* This reproduces bug #35350 where 'use no memo' doesn't apply recursively.
*/
function ParentComponent(props) {
'use no memo';

// This nested function has a component-like name but should NOT be compiled
// because the parent has 'use no memo'
function NestedComponent() {
return <div>{props.value}</div>;
}

return props.render(NestedComponent);
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [{value: 'test', render: C => C()}],
isComponent: true,
};

```

## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)

/**
* Test that nested component-like functions are NOT compiled when parent has 'use no memo'.
* This reproduces bug #35350 where 'use no memo' doesn't apply recursively.
*/
function ParentComponent(props) {
"use no memo";

// This nested function has a component-like name but should NOT be compiled
// because the parent has 'use no memo'
function NestedComponent() {
return <div>{props.value}</div>;
}

return props.render(NestedComponent);
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [
{
value: "test",
render: (C) => {
const $ = _c(2);
let t0;
if ($[0] !== C) {
t0 = C();
$[0] = C;
$[1] = t0;
} else {
t0 = $[1];
}
return t0;
},
},
],
isComponent: true,
};

```

### Eval output
(kind: ok) <div>test</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @compilationMode(infer)

/**
* Test that nested component-like functions are NOT compiled when parent has 'use no memo'.
* This reproduces bug #35350 where 'use no memo' doesn't apply recursively.
*/
function ParentComponent(props) {
'use no memo';

// This nested function has a component-like name but should NOT be compiled
// because the parent has 'use no memo'
function NestedComponent() {
return <div>{props.value}</div>;
}

return props.render(NestedComponent);
}

export const FIXTURE_ENTRYPOINT = {
fn: ParentComponent,
params: [{value: 'test', render: C => C()}],
isComponent: true,
};
Loading