@samitouri / QOS-React / commits / 152bfe3769

[compiler][rfc] Hacky retry pipeline for fire (#32164)

Hacky retry pipeline for when transforming `fire(...)` calls encounters validation, todo, or memoization invariant bailouts. Would love feedback on how we implement this to be extensible to other compiler non-memoization features (e.g. inlineJSX) Some observations: - Compiler "front-end" passes (e.g. lower, type, effect, and mutability inferences) should be shared for all compiler features -- memo and otherwise - Many passes (anything dealing with reactive scope ranges, scope blocks / dependencies, and optimizations such as ReactiveIR #31974) can be left out of the retry pipeline. This PR hackily skips memoization features by removing reactive scope creation, but we probably should restructure the pipeline to skip these entirely on a retry - We should maintain a canonical set of "validation flags" Note the newly added fixtures are prefixed with `bailout-...` when the retry fire pipeline is used. These fixture outputs contain correctly inserted `useFire` calls and no memoization.

mofeiZ committed Jan 31, 2025 at 15:57 UTC 152bfe3769f87e29c8d68cb87fdb608d2483b7f1
21 files changed +617 -90
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+6 -3
@@ -162,7 +162,8 @@ function runWithEnvironment(
162 if (
163 !env.config.enablePreserveExistingManualUseMemo &&
164 !env.config.disableMemoizationForDebugging &&
165 - !env.config.enableChangeDetectionForDebugging
165 + !env.config.enableChangeDetectionForDebugging &&
166 + !env.config.enableMinimalTransformsForRetry
167 ) {
168 dropManualMemoization(hir);
169 log({kind: 'hir', name: 'DropManualMemoization', value: hir});
@@ -279,8 +280,10 @@ function runWithEnvironment(
280 value: hir,
281 });
282
282 - inferReactiveScopeVariables(hir);
283 - log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
283 + if (!env.config.enableMinimalTransformsForRetry) {
284 + inferReactiveScopeVariables(hir);
285 + log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
286 + }
287
288 const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
289 log({
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+75 -48
@@ -16,6 +16,7 @@ import {
16 EnvironmentConfig,
17 ExternalFunction,
18 ReactFunctionType,
19 + MINIMAL_RETRY_CONFIG,
20 tryParseExternalFunction,
21 } from '../HIR/Environment';
22 import {CodegenFunction} from '../ReactiveScopes';
@@ -382,66 +383,92 @@ export function compileProgram(
383 );
384 }
385
385 - let compiledFn: CodegenFunction;
386 - try {
387 - /**
388 - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the
389 - * Program node itself. We need to figure out whether an eslint suppression range
390 - * applies to this function first.
391 - */
392 - const suppressionsInFunction = filterSuppressionsThatAffectFunction(
393 - suppressions,
394 - fn,
395 - );
396 - if (suppressionsInFunction.length > 0) {
397 - const lintError = suppressionsToCompilerError(suppressionsInFunction);
398 - if (optOutDirectives.length > 0) {
399 - logError(lintError, pass, fn.node.loc ?? null);
400 - } else {
401 - handleError(lintError, pass, fn.node.loc ?? null);
402 - }
403 - return null;
386 + /**
387 + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the
388 + * Program node itself. We need to figure out whether an eslint suppression range
389 + * applies to this function first.
390 + */
391 + const suppressionsInFunction = filterSuppressionsThatAffectFunction(
392 + suppressions,
393 + fn,
394 + );
395 + let compileResult:
396 + | {kind: 'compile'; compiledFn: CodegenFunction}
397 + | {kind: 'error'; error: unknown};
398 + if (suppressionsInFunction.length > 0) {
399 + compileResult = {
400 + kind: 'error',
401 + error: suppressionsToCompilerError(suppressionsInFunction),
402 + };
403 + } else {
404 + try {
405 + compileResult = {
406 + kind: 'compile',
407 + compiledFn: compileFn(
408 + fn,
409 + environment,
410 + fnType,
411 + useMemoCacheIdentifier.name,
412 + pass.opts.logger,
413 + pass.filename,
414 + pass.code,
415 + ),
416 + };
417 + } catch (err) {
418 + compileResult = {kind: 'error', error: err};
419 }
405 -
406 - compiledFn = compileFn(
407 - fn,
408 - environment,
409 - fnType,
410 - useMemoCacheIdentifier.name,
411 - pass.opts.logger,
412 - pass.filename,
413 - pass.code,
414 - );
415 - pass.opts.logger?.logEvent(pass.filename, {
416 - kind: 'CompileSuccess',
417 - fnLoc: fn.node.loc ?? null,
418 - fnName: compiledFn.id?.name ?? null,
419 - memoSlots: compiledFn.memoSlotsUsed,
420 - memoBlocks: compiledFn.memoBlocks,
421 - memoValues: compiledFn.memoValues,
422 - prunedMemoBlocks: compiledFn.prunedMemoBlocks,
423 - prunedMemoValues: compiledFn.prunedMemoValues,
424 - });
425 - } catch (err) {
420 + }
421 + // If non-memoization features are enabled, retry regardless of error kind
422 + if (compileResult.kind === 'error' && environment.enableFire) {
423 + try {
424 + compileResult = {
425 + kind: 'compile',
426 + compiledFn: compileFn(
427 + fn,
428 + {
429 + ...environment,
430 + ...MINIMAL_RETRY_CONFIG,
431 + },
432 + fnType,
433 + useMemoCacheIdentifier.name,
434 + pass.opts.logger,
435 + pass.filename,
436 + pass.code,
437 + ),
438 + };
439 + } catch (err) {
440 + compileResult = {kind: 'error', error: err};
441 + }
442 + }
443 + if (compileResult.kind === 'error') {
444 /**
445 * If an opt out directive is present, log only instead of throwing and don't mark as
446 * containing a critical error.
447 */
430 - if (fn.node.body.type === 'BlockStatement') {
431 - if (optOutDirectives.length > 0) {
432 - logError(err, pass, fn.node.loc ?? null);
433 - return null;
434 - }
448 + if (optOutDirectives.length > 0) {
449 + logError(compileResult.error, pass, fn.node.loc ?? null);
450 + } else {
451 + handleError(compileResult.error, pass, fn.node.loc ?? null);
452 }
436 - handleError(err, pass, fn.node.loc ?? null);
453 return null;
454 }
455
456 + pass.opts.logger?.logEvent(pass.filename, {
457 + kind: 'CompileSuccess',
458 + fnLoc: fn.node.loc ?? null,
459 + fnName: compileResult.compiledFn.id?.name ?? null,
460 + memoSlots: compileResult.compiledFn.memoSlotsUsed,
461 + memoBlocks: compileResult.compiledFn.memoBlocks,
462 + memoValues: compileResult.compiledFn.memoValues,
463 + prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks,
464 + prunedMemoValues: compileResult.compiledFn.prunedMemoValues,
465 + });
466 +
467 /**
468 * Always compile functions with opt in directives.
469 */
470 if (optInDirectives.length > 0) {
444 - return compiledFn;
471 + return compileResult.compiledFn;
472 } else if (pass.opts.compilationMode === 'annotation') {
473 /**
474 * No opt-in directive in annotation mode, so don't insert the compiled function.
@@ -467,7 +494,7 @@ export function compileProgram(
494 }
495
496 if (!pass.opts.noEmit) {
470 - return compiledFn;
497 + return compileResult.compiledFn;
498 }
499 return null;
500 };
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+13
@@ -552,6 +552,8 @@ const EnvironmentConfigSchema = z.object({
552 */
553 disableMemoizationForDebugging: z.boolean().default(false),
554
555 + enableMinimalTransformsForRetry: z.boolean().default(false),
556 +
557 /**
558 * When true, rather using memoized values, the compiler will always re-compute
559 * values, and then use a heuristic to compare the memoized value to the newly
@@ -626,6 +628,17 @@ const EnvironmentConfigSchema = z.object({
628
629 export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
630
631 +export const MINIMAL_RETRY_CONFIG: PartialEnvironmentConfig = {
632 + validateHooksUsage: false,
633 + validateRefAccessDuringRender: false,
634 + validateNoSetStateInRender: false,
635 + validateNoSetStateInPassiveEffects: false,
636 + validateNoJSXInTryStatements: false,
637 + validateMemoizedEffectDependencies: false,
638 + validateNoCapitalizedCalls: null,
639 + validateBlocklistedImports: null,
640 + enableMinimalTransformsForRetry: true,
641 +};
642 /**
643 * For test fixtures and playground only.
644 *
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts
+1 -1
@@ -241,7 +241,7 @@ export default function inferReferenceEffects(
241
242 if (options.isFunctionExpression) {
243 fn.effects = functionEffects;
244 - } else {
244 + } else if (!fn.env.config.enableMinimalTransformsForRetry) {
245 raiseFunctionEffectErrors(functionEffects);
246 }
247 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateNoCapitalizedCalls @enableFire
6 +import {fire} from 'react';
7 +const CapitalizedCall = require('shared-runtime').sum;
8 +
9 +function Component({prop1, bar}) {
10 + const foo = () => {
11 + console.log(prop1);
12 + };
13 + useEffect(() => {
14 + fire(foo(prop1));
15 + fire(foo());
16 + fire(bar());
17 + });
18 +
19 + return CapitalizedCall();
20 +}
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire
28 +import { fire } from "react";
29 +const CapitalizedCall = require("shared-runtime").sum;
30 +
31 +function Component(t0) {
32 + const { prop1, bar } = t0;
33 + const foo = () => {
34 + console.log(prop1);
35 + };
36 + const t1 = useFire(foo);
37 + const t2 = useFire(bar);
38 +
39 + useEffect(() => {
40 + t1(prop1);
41 + t1();
42 + t2();
43 + });
44 + return CapitalizedCall();
45 +}
46 +
47 +```
48 +
49 +### Eval output
50 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js new
+16
@@ -0,0 +1,16 @@
1 +// @validateNoCapitalizedCalls @enableFire
2 +import {fire} from 'react';
3 +const CapitalizedCall = require('shared-runtime').sum;
4 +
5 +function Component({prop1, bar}) {
6 + const foo = () => {
7 + console.log(prop1);
8 + };
9 + useEffect(() => {
10 + fire(foo(prop1));
11 + fire(foo());
12 + fire(bar());
13 + });
14 +
15 + return CapitalizedCall();
16 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md new
+55
@@ -0,0 +1,55 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableFire
6 +import {useRef} from 'react';
7 +
8 +function Component({props, bar}) {
9 + const foo = () => {
10 + console.log(props);
11 + };
12 + useEffect(() => {
13 + fire(foo(props));
14 + fire(foo());
15 + fire(bar());
16 + });
17 +
18 + const ref = useRef(null);
19 + // eslint-disable-next-line react-hooks/rules-of-hooks
20 + ref.current = 'bad';
21 + return <button ref={ref} />;
22 +}
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { useFire } from "react/compiler-runtime"; // @enableFire
30 +import { useRef } from "react";
31 +
32 +function Component(t0) {
33 + const { props, bar } = t0;
34 + const foo = () => {
35 + console.log(props);
36 + };
37 + const t1 = useFire(foo);
38 + const t2 = useFire(bar);
39 +
40 + useEffect(() => {
41 + t1(props);
42 + t1();
43 + t2();
44 + });
45 +
46 + const ref = useRef(null);
47 +
48 + ref.current = "bad";
49 + return <button ref={ref} />;
50 +}
51 +
52 +```
53 +
54 +### Eval output
55 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js new
+18
@@ -0,0 +1,18 @@
1 +// @enableFire
2 +import {useRef} from 'react';
3 +
4 +function Component({props, bar}) {
5 + const foo = () => {
6 + console.log(props);
7 + };
8 + useEffect(() => {
9 + fire(foo(props));
10 + fire(foo());
11 + fire(bar());
12 + });
13 +
14 + const ref = useRef(null);
15 + // eslint-disable-next-line react-hooks/rules-of-hooks
16 + ref.current = 'bad';
17 + return <button ref={ref} />;
18 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validatePreserveExistingMemoizationGuarantees @enableFire
6 +import {fire} from 'react';
7 +import {sum} from 'shared-runtime';
8 +
9 +function Component({prop1, bar}) {
10 + const foo = () => {
11 + console.log(prop1);
12 + };
13 + useEffect(() => {
14 + fire(foo(prop1));
15 + fire(foo());
16 + fire(bar());
17 + });
18 +
19 + return useMemo(() => sum(bar), []);
20 +}
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire
28 +import { fire } from "react";
29 +import { sum } from "shared-runtime";
30 +
31 +function Component(t0) {
32 + const { prop1, bar } = t0;
33 + const foo = () => {
34 + console.log(prop1);
35 + };
36 + const t1 = useFire(foo);
37 + const t2 = useFire(bar);
38 +
39 + useEffect(() => {
40 + t1(prop1);
41 + t1();
42 + t2();
43 + });
44 + return useMemo(() => sum(bar), []);
45 +}
46 +
47 +```
48 +
49 +### Eval output
50 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js new
+16
@@ -0,0 +1,16 @@
1 +// @validatePreserveExistingMemoizationGuarantees @enableFire
2 +import {fire} from 'react';
3 +import {sum} from 'shared-runtime';
4 +
5 +function Component({prop1, bar}) {
6 + const foo = () => {
7 + console.log(prop1);
8 + };
9 + useEffect(() => {
10 + fire(foo(prop1));
11 + fire(foo());
12 + fire(bar());
13 + });
14 +
15 + return useMemo(() => sum(bar), []);
16 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md new
+42
@@ -0,0 +1,42 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableFire
6 +import {fire} from 'react';
7 +
8 +function Component({prop1}) {
9 + const foo = () => {
10 + console.log(prop1);
11 + };
12 + useEffect(() => {
13 + fire(foo(prop1));
14 + });
15 + prop1.value += 1;
16 +}
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +import { useFire } from "react/compiler-runtime"; // @enableFire
24 +import { fire } from "react";
25 +
26 +function Component(t0) {
27 + const { prop1 } = t0;
28 + const foo = () => {
29 + console.log(prop1);
30 + };
31 + const t1 = useFire(foo);
32 +
33 + useEffect(() => {
34 + t1(prop1);
35 + });
36 + prop1.value = prop1.value + 1;
37 +}
38 +
39 +```
40 +
41 +### Eval output
42 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js new
+12
@@ -0,0 +1,12 @@
1 +// @enableFire
2 +import {fire} from 'react';
3 +
4 +function Component({prop1}) {
5 + const foo = () => {
6 + console.log(prop1);
7 + };
8 + useEffect(() => {
9 + fire(foo(prop1));
10 + });
11 + prop1.value += 1;
12 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md new
+51
@@ -0,0 +1,51 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @flow @enableFire
6 +import {fire} from 'react';
7 +import {print} from 'shared-runtime';
8 +
9 +component Component(prop1, ref) {
10 + const foo = () => {
11 + console.log(prop1);
12 + };
13 + useEffect(() => {
14 + fire(foo(prop1));
15 + bar();
16 + fire(foo());
17 + });
18 +
19 + print(ref.current);
20 + return null;
21 +}
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { useFire } from "react/compiler-runtime";
29 +import { fire } from "react";
30 +import { print } from "shared-runtime";
31 +
32 +const Component = React.forwardRef(Component_withRef);
33 +function Component_withRef(t0, ref) {
34 + const { prop1 } = t0;
35 + const foo = () => {
36 + console.log(prop1);
37 + };
38 + const t1 = useFire(foo);
39 + useEffect(() => {
40 + t1(prop1);
41 + bar();
42 + t1();
43 + });
44 + print(ref.current);
45 + return null;
46 +}
47 +
48 +```
49 +
50 +### Eval output
51 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js new
+17
@@ -0,0 +1,17 @@
1 +// @flow @enableFire
2 +import {fire} from 'react';
3 +import {print} from 'shared-runtime';
4 +
5 +component Component(prop1, ref) {
6 + const foo = () => {
7 + console.log(prop1);
8 + };
9 + useEffect(() => {
10 + fire(foo(prop1));
11 + bar();
12 + fire(foo());
13 + });
14 +
15 + print(ref.current);
16 + return null;
17 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md new
+49
@@ -0,0 +1,49 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableFire
6 +import {fire} from 'react';
7 +
8 +/**
9 + * Note that a react compiler-based transform still has limitations on JS syntax.
10 + * In practice, we expect to surface these as actionable errors to the user, in
11 + * the same way that invalid `fire` calls error.
12 + */
13 +function Component({prop1}) {
14 + const foo = () => {
15 + try {
16 + console.log(prop1);
17 + } finally {
18 + console.log('jbrown215');
19 + }
20 + };
21 + useEffect(() => {
22 + fire(foo());
23 + });
24 +}
25 +
26 +```
27 +
28 +
29 +## Error
30 +
31 +```
32 + 9 | function Component({prop1}) {
33 + 10 | const foo = () => {
34 +> 11 | try {
35 + | ^^^^^
36 +> 12 | console.log(prop1);
37 + | ^^^^^^^^^^^^^^^^^^^^^^^^^
38 +> 13 | } finally {
39 + | ^^^^^^^^^^^^^^^^^^^^^^^^^
40 +> 14 | console.log('jbrown215');
41 + | ^^^^^^^^^^^^^^^^^^^^^^^^^
42 +> 15 | }
43 + | ^^^^^^ Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)
44 + 16 | };
45 + 17 | useEffect(() => {
46 + 18 | fire(foo());
47 +```
48 +
49 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js new
+20
@@ -0,0 +1,20 @@
1 +// @enableFire
2 +import {fire} from 'react';
3 +
4 +/**
5 + * Note that a react compiler-based transform still has limitations on JS syntax.
6 + * In practice, we expect to surface these as actionable errors to the user, in
7 + * the same way that invalid `fire` calls error.
8 + */
9 +function Component({prop1}) {
10 + const foo = () => {
11 + try {
12 + console.log(prop1);
13 + } finally {
14 + console.log('jbrown215');
15 + }
16 + };
17 + useEffect(() => {
18 + fire(foo());
19 + });
20 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/todo-use-no-memo.expect.md new
+47
@@ -0,0 +1,47 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableFire
6 +import {fire} from 'react';
7 +
8 +function Component({props, bar}) {
9 + 'use no memo';
10 + const foo = () => {
11 + console.log(props);
12 + };
13 + useEffect(() => {
14 + fire(foo(props));
15 + fire(foo());
16 + fire(bar());
17 + });
18 +
19 + return null;
20 +}
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +// @enableFire
28 +import { fire } from "react";
29 +
30 +function Component({ props, bar }) {
31 + "use no memo";
32 + const foo = () => {
33 + console.log(props);
34 + };
35 + useEffect(() => {
36 + fire(foo(props));
37 + fire(foo());
38 + fire(bar());
39 + });
40 +
41 + return null;
42 +}
43 +
44 +```
45 +
46 +### Eval output
47 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/todo-use-no-memo.js new
+16
@@ -0,0 +1,16 @@
1 +// @enableFire
2 +import {fire} from 'react';
3 +
4 +function Component({props, bar}) {
5 + 'use no memo';
6 + const foo = () => {
7 + console.log(props);
8 + };
9 + useEffect(() => {
10 + fire(foo(props));
11 + fire(foo());
12 + fire(bar());
13 + });
14 +
15 + return null;
16 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @enableFire
6 +import {fire, useEffect} from 'react';
7 +import {Stringify} from 'shared-runtime';
8 +
9 +/**
10 + * When @enableFire is specified, retry compilation with validation passes (e.g.
11 + * hook usage) disabled
12 + */
13 +function Component(props) {
14 + const foo = props => {
15 + console.log(props);
16 + };
17 +
18 + if (props.cond) {
19 + useEffect(() => {
20 + fire(foo(props));
21 + });
22 + }
23 +
24 + return <Stringify />;
25 +}
26 +
27 +```
28 +
29 +## Code
30 +
31 +```javascript
32 +import { useFire } from "react/compiler-runtime"; // @enableFire
33 +import { fire, useEffect } from "react";
34 +import { Stringify } from "shared-runtime";
35 +
36 +/**
37 + * When @enableFire is specified, retry compilation with validation passes (e.g.
38 + * hook usage) disabled
39 + */
40 +function Component(props) {
41 + const foo = _temp;
42 + if (props.cond) {
43 + const t0 = useFire(foo);
44 + useEffect(() => {
45 + t0(props);
46 + });
47 + }
48 + return <Stringify />;
49 +}
50 +function _temp(props_0) {
51 + console.log(props_0);
52 +}
53 +
54 +```
55 +
56 +### Eval output
57 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js renamed
+6 -1
@@ -1,6 +1,11 @@
1 // @enableFire
2 import {fire, useEffect} from 'react';
3 +import {Stringify} from 'shared-runtime';
4
5 +/**
6 + * When @enableFire is specified, retry compilation with validation passes (e.g.
7 + * hook usage) disabled
8 + */
9 function Component(props) {
10 const foo = props => {
11 console.log(props);
@@ -12,5 +17,5 @@ function Component(props) {
17 });
18 }
19
15 - return null;
20 + return <Stringify />;
21 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-conditional-use-effect.expect.md deleted
-37
@@ -1,37 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @enableFire
6 -import {fire, useEffect} from 'react';
7 -
8 -function Component(props) {
9 - const foo = props => {
10 - console.log(props);
11 - };
12 -
13 - if (props.cond) {
14 - useEffect(() => {
15 - fire(foo(props));
16 - });
17 - }
18 -
19 - return null;
20 -}
21 -
22 -```
23 -
24 -
25 -## Error
26 -
27 -```
28 - 8 |
29 - 9 | if (props.cond) {
30 -> 10 | useEffect(() => {
31 - | ^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (10:10)
32 - 11 | fire(foo(props));
33 - 12 | });
34 - 13 | }
35 -```
36 -
37 -
\ No newline at end of file