Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[compiler] detect and throw on untransformed required features #32512

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

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

This PR also throws retry pipeline errors, which shows better errors than the current generic TODO message

Stack created with Sapling. Best reviewed with ReviewStack.

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
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.
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)

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*

This PR also throws retry pipeline errors, which shows better errors than the current generic TODO message
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?


/**
* 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?

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)

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

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.

3 participants