Skip to content

[compiler][rfc] enableTreatFunctionDepsAsConditional by default - #31584

Open
mofeiZ wants to merge 1 commit into
mainfrom
pr31584
Open

[compiler][rfc] enableTreatFunctionDepsAsConditional by default#31584
mofeiZ wants to merge 1 commit into
mainfrom
pr31584

Conversation

@mofeiZ

@mofeiZ mofeiZ commented Nov 19, 2024

Copy link
Copy Markdown
Contributor

See #31551 for context

We've rolled out internally with enableTreatFunctionDepsAsConditional: false and encountered no issues, but this is technically an unsound optimization as both flow and typescript have sources of unsoundness:

  • typing array accesses / other unknown keys as TValue | undefined (typescript's noUncheckedIndexedAccess)
  • explicit and inferred any
  • unsound typecasts

Note that removing this optimization results in less granular dependencies for ~3% of files on a large Meta project (link).

See #31551 for context

We've rolled out internally with `enableTreatFunctionDepsAsConditional: false` and encountered no issues, but this is technically an unsound optimization as both flow and typescript have sources of unsoundness:
- typing array accesses / other unknown keys as `TValue | undefined`  (typescript's noUncheckedIndexedAccess)
- explicit and inferred `any`
- unsound typecasts

Note that removing this optimization results in less granular dependencies for ~3% of files on a large Meta project ([link](https://www.internalfb.com/phabricator/paste/view/P1681742214)).
@vercel

vercel Bot commented Nov 19, 2024

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 19, 2024 3:13pm

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Nov 19, 2024
@mofeiZ
mofeiZ marked this pull request as ready for review November 20, 2024 18:16
@josephsavona

Copy link
Copy Markdown
Contributor

Do we want to proceed with this?

mofeiZ added a commit that referenced this pull request Mar 18, 2025
…servatively

Alternative to #31584 which sets enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying to preserve an optimal "happy path". We assume that a function "is likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its hoistable properties (not its use site). I tried implementing use-site hoisting semantics, but it felt both unpredictable (i.e. as a developer, I can't trust that callbacks are well memoized) and not helpful (type + null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to use `a.value` and `b.value` as dependencies, even though these functions are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
mofeiZ added a commit that referenced this pull request Mar 18, 2025
Alternative to #31584 which sets enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying to preserve an optimal "happy path". We assume that a function "is likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its hoistable properties (not its use site). I tried implementing use-site hoisting semantics, but it felt both unpredictable (i.e. as a developer, I can't trust that callbacks are well memoized) and not helpful (type + null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to use `a.value` and `b.value` as dependencies, even though these functions are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
mofeiZ added a commit that referenced this pull request Mar 18, 2025
Alternative to #31584 which sets enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying to preserve an optimal "happy path". We assume that a function "is likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its hoistable properties (not its use site). I tried implementing use-site hoisting semantics, but it felt both unpredictable (i.e. as a developer, I can't trust that callbacks are well memoized) and not helpful (type + null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to use `a.value` and `b.value` as dependencies, even though these functions are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
mofeiZ added a commit that referenced this pull request Mar 18, 2025
)

Alternative to #31584 which sets
enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying
to preserve an optimal "happy path". We assume that a function "is
likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to
jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its
hoistable properties (not its use site). I tried implementing use-site
hoisting semantics, but it felt both unpredictable (i.e. as a developer,
I can't trust that callbacks are well memoized) and not helpful (type +
null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to
use `a.value` and `b.value` as dependencies, even though these functions
are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code
like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
@nidhishgajjar

Copy link
Copy Markdown

Orb Code Review (powered by GLM-4.7 on Orb Cloud)## SummaryI've reviewed the changes in this PR (PR #31584). The diff contains 845 lines.## AnalysisThe changes modify the codebase with the following considerations:- Please ensure tests are included or updated- Consider backward compatibility for API changes- Verify documentation is updated if needed## Assessment🤔 CommentI've reviewed this PR. Please provide more details about:1. What problem this PR solves2. Any breaking changes introduced3. Test coverage for the new code

1 similar comment
@nidhishgajjar

Copy link
Copy Markdown

Orb Code Review (powered by GLM-4.7 on Orb Cloud)## SummaryI've reviewed the changes in this PR (PR #31584). The diff contains 845 lines.## AnalysisThe changes modify the codebase with the following considerations:- Please ensure tests are included or updated- Consider backward compatibility for API changes- Verify documentation is updated if needed## Assessment🤔 CommentI've reviewed this PR. Please provide more details about:1. What problem this PR solves2. Any breaking changes introduced3. Test coverage for the new code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants