-
Notifications
You must be signed in to change notification settings - Fork 49.7k
[compiler] detect and throw on untransformed required features #32512
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
Conversation
| 4 | function nonReactFn(arg) { | ||
| > 5 | useEffect(() => [1, 2, arg]); | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Untransformed reference to experimental compiler-only feature (5:5) | ||
| 6 | } | ||
| 7 | |
There was a problem hiding this comment.
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.
| > 20 | useEffect(f); | ||
| | ^^^^^^^^^^^^ Todo: Untransformed reference to experimental compiler-only feature (20:20) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
| logError(err, pass, fn.node.loc ?? null); | ||
| throw err; |
There was a problem hiding this comment.
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
- only retry compilation if fire or effect dependencies might be called
- stash or return this error to later throw (in
ValidateNoUntransformed)
There was a problem hiding this 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); |
There was a problem hiding this comment.
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
}There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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(() => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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".
There was a problem hiding this 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.
…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
…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*
|
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 |
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)
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
firewill throw at runtime anyways, so let's fail the build to catch these earlier.Note that with this fails the build regardless of panicThreshold