Skip to content

Conversation

@mofeiZ
Copy link
Contributor

@mofeiZ mofeiZ commented Mar 4, 2025

Traverse program after running compiler transform to find untransformed references to compiler features (e.g. inferEffectDeps, fire).

Hard error to fail the babel pipeline when the compiler fails to transform these features to give predictable runtime semantics. Untransformed calls to functions like fire will throw at runtime anyways, so let's fail the build to catch these earlier.

Note that with this fails the build regardless of panicThreshold

Comment on lines 19 to 23
4 | function nonReactFn(arg) {
> 5 | useEffect(() => [1, 2, arg]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Untransformed reference to experimental compiler-only feature (5:5)
6 | }
7 |
Copy link
Contributor Author

@mofeiZ mofeiZ Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erroring on an untransformed call anywhere within the program is intentional, as we expect untransformed calls to be invalid / throw at runtime.

Comment on lines 35 to 36
> 20 | useEffect(f);
| ^^^^^^^^^^^^ Todo: Untransformed reference to experimental compiler-only feature (20:20)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here -- this error is intentional as we expect untransformed calls to be invalid / throw at runtime.

## Input

```javascript
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these fixtures error even with @panicThreshold(none). This PR changes inferEffectDeps to be a "required" feature (e.g. if enabled, relevant code must either be transformed or fail compilation)

@mofeiZ mofeiZ marked this pull request as ready for review March 4, 2025 17:24
@mofeiZ mofeiZ changed the title [compiler] detect and hard error on untransformed required features [compiler] detect and throw on untransformed required features Mar 4, 2025
Comment on lines 441 to 442
logError(err, pass, fn.node.loc ?? null);
throw err;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite right -- we want

  1. only retry compilation if fire or effect dependencies might be called
  2. stash or return this error to later throw (in ValidateNoUntransformed)

Copy link
Contributor

@jbrown215 jbrown215 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behaviors look good to me! We'll definitely need more actionable error messages so that people can figure out how to make the build errors go away, but this is an excellent start. Left some clarifying questions inline.

}

// No inferred dep array, the argument is not a lambda
useEffect(f);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are useEffect wrappers that forward a function + dep array that will block us from enabling this rule, e.g.:

function useMyEffect(fn, deps) {
  useEffect(fn, deps);
  // ... other stuff
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should allow non-lambda arguments so long as a dep array is provided, I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! Right now we only matches inferEffectDependencies function calls with the same number of arguments as configured with numRequiredArgs

The example you gave useEffect(fn, args) wouldn't match the test runner config since it has two arguments instead of one


/**
* Note that a react compiler-based transform still has limitations on JS syntax.
* We should surface these as actionable lint / build errors to devs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix here is to specify the dep array (for special effect, that's a second dep array), is that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep exactly -- I'll edit the error messages to be more actionable

import {useEffect} from 'react';

function Component({propVal}) {
'use no memo';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean 'use no memo' should not disable autodeps/fire? (that sounds right to me, just checking if that's your intent)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep that's right -- ideally we'd still run the compiler on use no memo (just with memoization disabled), but that's still a todo

function useFoo({cond}) {
const ref = useRef();
const derived = cond ? ref.current : makeObject();
useSpecialEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we currently depend on memoization? I thought we only depended on reactivity analysis

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the implementation currently depends on mutability analysis + reactive scopes. ref.current values are a bit of an edge case for mutable ranges, but this is really an implementation todo (specifically, the retry pipeline should not use reactive scopes for dependency analysis)

For devs, this should just an unknown compiler todo bailout with "please supply your own dependency array as the compiler could not transpile this".

Copy link
Contributor

@jackpope jackpope left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Walked through it together. Examples make sense.

Error messages do need improvement to be actionable but that can also be a follow up PR.

mofeiZ added a commit that referenced this pull request Mar 13, 2025
…32511)

Removes `EnvironmentConfig.enableMinimalTransformsForRetry` in favor of
`run` parameters. This is a minimal difference but lets us explicitly
opt out certain compiler passes based on mode parameters, instead of
environment configurations

Retry flags don't really make sense to have in `EnvironmentConfig`
anyways as the config is user-facing API, while retrying is a compiler
implementation detail.

(per @josephsavona's feedback
#32164 (comment))
> Re the "hacky" framing of this in the PR title: I think this is fine.
I can see having something like a compilation or output mode that we use
when running the pipeline. Rather than changing environment settings
when we re-run, various passes could take effect based on the
combination of the mode + env flags. The modes might be:
>
> * Full: transform, validate, memoize. This is the default today.
> * Transform: Along the lines of the backup mode in this PR. Only
applies transforms that do not require following the rules of React,
like `fire()`.
> * Validate: This could be used for ESLint.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32511).
* #32512
* __->__ #32511
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
…32511)

Removes `EnvironmentConfig.enableMinimalTransformsForRetry` in favor of
`run` parameters. This is a minimal difference but lets us explicitly
opt out certain compiler passes based on mode parameters, instead of
environment configurations

Retry flags don't really make sense to have in `EnvironmentConfig`
anyways as the config is user-facing API, while retrying is a compiler
implementation detail.

(per @josephsavona's feedback
#32164 (comment))
> Re the "hacky" framing of this in the PR title: I think this is fine.
I can see having something like a compilation or output mode that we use
when running the pipeline. Rather than changing environment settings
when we re-run, various passes could take effect based on the
combination of the mode + env flags. The modes might be:
>
> * Full: transform, validate, memoize. This is the default today.
> * Transform: Along the lines of the backup mode in this PR. Only
applies transforms that do not require following the rules of React,
like `fire()`.
> * Validate: This could be used for ESLint.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32511).
* #32512
* __->__ #32511

DiffTrain build for [7939d92](7939d92)
Traverse program after running compiler transform to find untransformed references to compiler features (e.g. `inferEffectDeps`, `fire`).

Hard error to fail the babel pipeline when the compiler fails to transform these features to give predictable runtime semantics. Untransformed calls to functions like `fire` will throw at runtime anyways, so let's fail the build to catch these earlier.

Note that with this fails the build *regardless of panicThreshold*
@mofeiZ
Copy link
Contributor Author

mofeiZ commented Mar 14, 2025

Update: synced offline with @jackpope to improve the error diagnostic. Also checked that this is correctly reported by our eslint plugin.

This PR should now be ready to merge

@mofeiZ mofeiZ merged commit 5398b71 into main Mar 14, 2025
33 of 34 checks passed
github-actions bot pushed a commit that referenced this pull request Mar 14, 2025
Traverse program after running compiler transform to find untransformed
references to compiler features (e.g. `inferEffectDeps`, `fire`).

Hard error to fail the babel pipeline when the compiler fails to
transform these features to give predictable runtime semantics.
Untransformed calls to functions like `fire` will throw at runtime
anyways, so let's fail the build to catch these earlier.

Note that with this fails the build *regardless of panicThreshold*

DiffTrain build for [5398b71](5398b71)
@poteto poteto deleted the pr32512 branch March 26, 2025 22:36
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