@samitouri / QOS-React-2 / commits / 1ce952e687

Support hoisting const variable declarations

This PR adds preliminary support for hoisting const variable declarations. We do this via BuildHIR when lowering top level statements in a BlockStatement, by first checking which bindings are in scope to be hoistable if referenced before they are declared. The declarations are then hoisted to their earliest point where they are referenced (ie the top level statement just before) as context variables. Later, prior to codegen, we restore the original source by removing the DeclareContexts and transforming their associated StoreContexts back. Support for hoisting other kinds of declarations will come in future PRs!

Lauren Tan committed Sep 21, 2023 at 17:00 UTC 1ce952e687b7d2031bf3d6aa69f917506bc2c2cb
24 files changed +650 -66
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+8
@@ -46,6 +46,7 @@ import {
46 promoteUsedTemporaries,
47 propagateScopeDependencies,
48 pruneAllReactiveScopes,
49 + pruneHoistedContexts,
50 pruneNonEscapingScopes,
51 pruneNonReactiveDependencies,
52 pruneUnusedLValues,
@@ -299,6 +300,13 @@ export function* run(
300 value: reactiveFunction,
301 });
302
303 + pruneHoistedContexts(reactiveFunction);
304 + yield log({
305 + kind: "reactive",
306 + name: "PruneHoistedContexts",
307 + value: reactiveFunction,
308 + });
309 +
310 const ast = codegenReactiveFunction(reactiveFunction).unwrap();
311 yield log({ kind: "ast", name: "Codegen", value: ast });
312
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+108 -4
@@ -5,7 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import { NodePath, Scope } from "@babel/traverse";
8 +import { Binding, NodePath, Scope } from "@babel/traverse";
9 import * as t from "@babel/types";
10 import { Expression } from "@babel/types";
11 import invariant from "invariant";
@@ -298,7 +298,108 @@ function lowerStatement(
298 }
299 case "BlockStatement": {
300 const stmt = stmtPath as NodePath<t.BlockStatement>;
301 - stmt.get("body").forEach((s) => lowerStatement(builder, s));
301 + const statements = stmt.get("body");
302 + const hoistableBindings: Set<Binding> = new Set();
303 +
304 + const recordDeclaration = (lval: NodePath<t.LVal>): void => {
305 + // TODO: support other kinds of declarations that might need to be hoisted
306 + switch (lval.type) {
307 + case "Identifier": {
308 + const lv = lval as NodePath<t.Identifier>;
309 + const binding = stmt.scope.getBinding(lv.node.name);
310 + if (binding != null) {
311 + hoistableBindings.delete(binding);
312 + }
313 + break;
314 + }
315 + }
316 + };
317 +
318 + for (const [, binding] of Object.entries(stmt.scope.bindings)) {
319 + // TODO: support other kinds of bindings
320 + if (binding.kind === "const") {
321 + if (
322 + binding.path.isVariableDeclarator() &&
323 + binding.path.get("id").isIdentifier()
324 + ) {
325 + hoistableBindings.add(binding);
326 + }
327 + }
328 + }
329 +
330 + for (const s of statements) {
331 + const hoistableIdentifiers = new Set<NodePath<t.Identifier>>();
332 + // After visiting the declaration, hoisting is no longer required
333 + // TODO: support other kinds of declarations
334 + if (s.isVariableDeclaration()) {
335 + for (const decl of s.get("declarations")) {
336 + recordDeclaration(decl.get("id"));
337 + }
338 + }
339 +
340 + // If we see a hoistable identifier before its declaration, it should be hoisted just
341 + // before the statement that references it
342 + s.traverse({
343 + Identifier(id: NodePath<t.Identifier>) {
344 + const binding = stmt.scope.getBinding(id.node.name);
345 + if (binding != null && hoistableBindings.has(binding)) {
346 + if (
347 + id.parentPath.isVariableDeclarator() ||
348 + // don't hoist MemberExpr `property`s, only their `object`
349 + (id.parentPath.isMemberExpression() &&
350 + id.parentPath.get("property") === id &&
351 + id.parentPath.node.computed === false)
352 + ) {
353 + return;
354 + }
355 + hoistableIdentifiers.add(id);
356 + }
357 + },
358 + });
359 +
360 + // Hoist declarations that need it to the earliest point where they are needed
361 + for (const id of hoistableIdentifiers) {
362 + const binding = stmt.scope.getBinding(id.node.name);
363 + CompilerError.invariant(binding != null, {
364 + reason: "Expected to find binding for hoisted identifier",
365 + description: `Could not find a binding for ${id.node.name}`,
366 + suggestions: null,
367 + loc: id.node.loc ?? GeneratedSource,
368 + });
369 + if (builder.environment.isHoistedIdentifier(binding.identifier)) {
370 + // Already hoisted
371 + continue;
372 + }
373 + if (!binding.path.isVariableDeclarator()) {
374 + builder.errors.push({
375 + severity: ErrorSeverity.Todo,
376 + reason: "Unsupported declaration type for hoisting",
377 + description: `${id.parentPath.type}`,
378 + suggestions: null,
379 + loc: id.parentPath.node.loc ?? GeneratedSource,
380 + });
381 + continue;
382 + }
383 + const identifier = builder.resolveIdentifier(id)!;
384 + const place: Place = {
385 + effect: Effect.Unknown,
386 + identifier,
387 + kind: "Identifier",
388 + loc: id.node.loc ?? GeneratedSource,
389 + };
390 + lowerValueToTemporary(builder, {
391 + kind: "DeclareContext",
392 + lvalue: {
393 + kind: InstructionKind.HoistedConst,
394 + place,
395 + },
396 + loc: id.node.loc ?? GeneratedSource,
397 + });
398 + builder.environment.addHoistedIdentifier(binding.identifier);
399 + }
400 + lowerStatement(builder, s);
401 + }
402 +
403 return;
404 }
405 case "BreakStatement": {
@@ -2894,13 +2995,16 @@ function lowerAssignment(
2995 node: lvalue.node,
2996 };
2997 }
2998 + const isHoistedIdentifier = builder.environment.isHoistedIdentifier(
2999 + lvalue.node
3000 + );
3001
3002 let temporary;
3003 if (builder.isContextIdentifier(lvalue)) {
2900 - if (kind !== InstructionKind.Reassign) {
3004 + if (kind !== InstructionKind.Reassign && !isHoistedIdentifier) {
3005 if (kind === InstructionKind.Const) {
3006 builder.errors.push({
2903 - reason: `Invalid declaration kind (const), this variable is reassigned later`,
3007 + reason: `[lowerAssignment] Invalid declaration kind (const), this variable is reassigned later`,
3008 severity: ErrorSeverity.InvalidJS,
3009 loc: lvalue.node.loc ?? null,
3010 suggestions: null,
compiler/packages/babel-plugin-react-forget/src/HIR/Environment.ts
+12
@@ -235,6 +235,7 @@ export class Environment {
235 enableForest: boolean;
236
237 #contextIdentifiers: Set<t.Identifier>;
238 + #hoistedIdentifiers: Set<t.Identifier>;
239
240 constructor(
241 config: EnvironmentConfig | null,
@@ -289,6 +290,7 @@ export class Environment {
290 this.enableForest = config?.enableForest ?? false;
291
292 this.#contextIdentifiers = contextIdentifiers;
293 + this.#hoistedIdentifiers = new Set();
294 }
295
296 get nextIdentifierId(): IdentifierId {
@@ -298,10 +300,15 @@ export class Environment {
300 get nextBlockId(): BlockId {
301 return makeBlockId(this.#nextBlock++);
302 }
303 +
304 isContextIdentifier(node: t.Identifier): boolean {
305 return this.#contextIdentifiers.has(node);
306 }
307
308 + isHoistedIdentifier(node: t.Identifier): boolean {
309 + return this.#hoistedIdentifiers.has(node);
310 + }
311 +
312 getGlobalDeclaration(name: string): Global | null {
313 let resolvedGlobal: Global | null = this.#globals.get(name) ?? null;
314 if (resolvedGlobal === null) {
@@ -356,6 +363,11 @@ export class Environment {
363 }
364 return null;
365 }
366 +
367 + addHoistedIdentifier(node: t.Identifier): void {
368 + this.#contextIdentifiers.add(node);
369 + this.#hoistedIdentifiers.add(node);
370 + }
371 }
372
373 // From https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#LL18C1-L23C2
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+7 -1
@@ -586,6 +586,11 @@ export enum InstructionKind {
586 * catch clause binding
587 */
588 Catch = "Catch",
589 +
590 + /**
591 + * hoisted const declarations
592 + */
593 + HoistedConst = "HoistedConst",
594 }
595
596 function _staticInvariantInstructionValueHasLocation(
@@ -641,6 +646,7 @@ export type CallExpression = {
646 *
647 * Operands are therefore always a Place.
648 */
649 +
650 export type InstructionValue =
651 | {
652 kind: "LoadLocal";
@@ -660,7 +666,7 @@ export type InstructionValue =
666 | {
667 kind: "DeclareContext";
668 lvalue: {
663 - kind: InstructionKind.Let;
669 + kind: InstructionKind.Let | InstructionKind.HoistedConst;
670 place: Place;
671 };
672 loc: SourceLocation;
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+3
@@ -602,6 +602,9 @@ export function printLValue(lval: LValue): string {
602 case InstructionKind.Catch: {
603 return `Catch ${lvalue}`;
604 }
605 + case InstructionKind.HoistedConst: {
606 + return `HoistedConst ${lvalue}$`;
607 + }
608 default: {
609 assertExhaustive(lval.kind, `Unexpected lvalue kind '${lval.kind}'`);
610 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+16
@@ -467,6 +467,13 @@ function codegenTerminal(
467 loc: iterableItem.loc,
468 suggestions: null,
469 });
470 + case InstructionKind.HoistedConst:
471 + CompilerError.invariant(false, {
472 + reason: "Unexpected HoistedConst variable in for-of collection",
473 + description: null,
474 + loc: iterableItem.loc,
475 + suggestions: null,
476 + });
477 default:
478 assertExhaustive(
479 iterableItem.value.lvalue.kind,
@@ -671,6 +678,15 @@ function codegenInstructionNullable(
678 case InstructionKind.Catch: {
679 return t.emptyStatement();
680 }
681 + case InstructionKind.HoistedConst: {
682 + CompilerError.invariant(false, {
683 + reason:
684 + "Expected HoistedConsts to have been pruned in PruneHoistedContexts",
685 + description: null,
686 + loc: instr.loc,
687 + suggestions: null,
688 + });
689 + }
690 default: {
691 assertExhaustive(kind, `Unexpected instruction kind '${kind}'`);
692 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PruneHoistedContexts.ts new
+64
@@ -0,0 +1,64 @@
1 +import {
2 + Identifier,
3 + InstructionKind,
4 + ReactiveFunction,
5 + ReactiveInstruction,
6 + ReactiveStatement,
7 +} from "../HIR";
8 +import {
9 + ReactiveFunctionTransform,
10 + Transformed,
11 + visitReactiveFunction,
12 +} from "./visitors";
13 +
14 +/**
15 + * Prunes DeclareContexts lowered for HoistedConsts, and transforms any references back to its
16 + * original instruction kind.
17 + */
18 +export function pruneHoistedContexts(fn: ReactiveFunction): void {
19 + const hoistedIdentifiers: HoistedIdentifiers = new Set();
20 + visitReactiveFunction(fn, new Visitor(), hoistedIdentifiers);
21 +}
22 +
23 +type HoistedIdentifiers = Set<Identifier>;
24 +
25 +class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
26 + override transformInstruction(
27 + instruction: ReactiveInstruction,
28 + state: HoistedIdentifiers
29 + ): Transformed<ReactiveStatement> {
30 + this.visitInstruction(instruction, state);
31 + if (
32 + instruction.value.kind === "DeclareContext" &&
33 + instruction.value.lvalue.kind === "HoistedConst"
34 + ) {
35 + state.add(instruction.value.lvalue.place.identifier);
36 + return { kind: "remove" };
37 + }
38 +
39 + if (
40 + instruction.value.kind === "StoreContext" &&
41 + state.has(instruction.value.lvalue.place.identifier)
42 + ) {
43 + return {
44 + kind: "replace",
45 + value: {
46 + kind: "instruction",
47 + instruction: {
48 + ...instruction,
49 + value: {
50 + ...instruction.value,
51 + lvalue: {
52 + ...instruction.value.lvalue,
53 + kind: InstructionKind.Const,
54 + },
55 + kind: "StoreLocal",
56 + },
57 + },
58 + },
59 + };
60 + }
61 +
62 + return { kind: "keep" };
63 + }
64 +}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts
+1
@@ -22,6 +22,7 @@ export { printReactiveFunction } from "./PrintReactiveFunction";
22 export { promoteUsedTemporaries } from "./PromoteUsedTemporaries";
23 export { propagateScopeDependencies } from "./PropagateScopeDependencies";
24 export { pruneAllReactiveScopes } from "./PruneAllReactiveScopes";
25 +export { pruneHoistedContexts } from "./PruneHoistedContexts";
26 export { pruneNonEscapingScopes } from "./PruneNonEscapingScopes";
27 export { pruneNonReactiveDependencies } from "./PruneNonReactiveDependencies";
28 export { pruneTemporaryLValues as pruneUnusedLValues } from "./PruneTemporaryLValues";
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md renamed
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.js renamed
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-hoisting-simple-function-expression.expect.md deleted
-31
@@ -1,31 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function hoisting() {
6 - const foo = () => {
7 - return bar();
8 - };
9 - const bar = () => {
10 - return 1;
11 - };
12 -
13 - return foo(); // OK: bar's value is only accessed outside of its TDZ
14 -}
15 -
16 -export const FIXTURE_ENTRYPOINT = {
17 - fn: hoisting,
18 - params: [],
19 - isComponent: false,
20 -};
21 -
22 -```
23 -
24 -
25 -## Error
26 -
27 -```
28 -[ReactForget] Todo: EnterSSA: Expected identifier to be defined before being used. Identifier bar$0 is undefined (5:7)
29 -```
30 -
31 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-hoisting-simple-let-const-declaration.expect.md deleted
-29
@@ -1,29 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function hoisting() {
6 - const foo = () => {
7 - return bar + baz;
8 - };
9 - let bar = 3;
10 - const baz = 2;
11 - return foo(); // OK: called outside of TDZ for bar/baz
12 -}
13 -
14 -export const FIXTURE_ENTRYPOINT = {
15 - fn: hoisting,
16 - params: [],
17 - isComponent: false,
18 -};
19 -
20 -```
21 -
22 -
23 -## Error
24 -
25 -```
26 -[ReactForget] Todo: EnterSSA: Expected identifier to be defined before being used. Identifier bar$0 is undefined (5:5)
27 -```
28 -
29 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-computed-member-expression.expect.md new
+67
@@ -0,0 +1,67 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function hoisting() {
6 + function onClick(x) {
7 + return x + bar["baz"];
8 + }
9 + function onClick2(x) {
10 + return x + bar[baz];
11 + }
12 + const baz = "baz";
13 + const bar = { baz: 1 };
14 +
15 + return <Button onClick={onClick} onClick2={onClick2} />;
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: hoisting,
20 + params: [],
21 + isComponent: false,
22 +};
23 +
24 +```
25 +
26 +## Code
27 +
28 +```javascript
29 +import { unstable_useMemoCache as useMemoCache } from "react";
30 +function hoisting() {
31 + const $ = useMemoCache(3);
32 + let onClick;
33 + let onClick2;
34 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
35 + onClick = function onClick(x) {
36 + return x + bar.baz;
37 + };
38 + onClick2 = function onClick2(x_0) {
39 + return x_0 + bar[baz];
40 + };
41 +
42 + const baz = "baz";
43 + const bar = { baz: 1 };
44 + $[0] = onClick;
45 + $[1] = onClick2;
46 + } else {
47 + onClick = $[0];
48 + onClick2 = $[1];
49 + }
50 + let t0;
51 + if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
52 + t0 = <Button onClick={onClick} onClick2={onClick2} />;
53 + $[2] = t0;
54 + } else {
55 + t0 = $[2];
56 + }
57 + return t0;
58 +}
59 +
60 +export const FIXTURE_ENTRYPOINT = {
61 + fn: hoisting,
62 + params: [],
63 + isComponent: false,
64 +};
65 +
66 +```
67 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-computed-member-expression.js new
+18
@@ -0,0 +1,18 @@
1 +function hoisting() {
2 + function onClick(x) {
3 + return x + bar["baz"];
4 + }
5 + function onClick2(x) {
6 + return x + bar[baz];
7 + }
8 + const baz = "baz";
9 + const bar = { baz: 1 };
10 +
11 + return <Button onClick={onClick} onClick2={onClick2} />;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: hoisting,
16 + params: [],
17 + isComponent: false,
18 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-member-expression.expect.md new
+56
@@ -0,0 +1,56 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function hoisting() {
6 + function onClick(x) {
7 + return x + bar.baz;
8 + }
9 + const bar = { baz: 1 };
10 +
11 + return <Button onClick={onClick} />;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: hoisting,
16 + params: [],
17 + isComponent: false,
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { unstable_useMemoCache as useMemoCache } from "react";
26 +function hoisting() {
27 + const $ = useMemoCache(2);
28 + let onClick;
29 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 + onClick = function onClick(x) {
31 + return x + bar.baz;
32 + };
33 +
34 + const bar = { baz: 1 };
35 + $[0] = onClick;
36 + } else {
37 + onClick = $[0];
38 + }
39 + let t0;
40 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
41 + t0 = <Button onClick={onClick} />;
42 + $[1] = t0;
43 + } else {
44 + t0 = $[1];
45 + }
46 + return t0;
47 +}
48 +
49 +export const FIXTURE_ENTRYPOINT = {
50 + fn: hoisting,
51 + params: [],
52 + isComponent: false,
53 +};
54 +
55 +```
56 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-member-expression.js new
+14
@@ -0,0 +1,14 @@
1 +function hoisting() {
2 + function onClick(x) {
3 + return x + bar.baz;
4 + }
5 + const bar = { baz: 1 };
6 +
7 + return <Button onClick={onClick} />;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: hoisting,
12 + params: [],
13 + isComponent: false,
14 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-nested-const-declaration-2.expect.md new
+64
@@ -0,0 +1,64 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function hoisting(cond) {
6 + let items = [];
7 + if (cond) {
8 + const foo = () => {
9 + items.push(bar());
10 + };
11 + const bar = () => true;
12 + foo();
13 + }
14 + return items;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: hoisting,
19 + params: [true],
20 + isComponent: false,
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { unstable_useMemoCache as useMemoCache } from "react";
29 +function hoisting(cond) {
30 + const $ = useMemoCache(3);
31 + const c_0 = $[0] !== cond;
32 + let items;
33 + if (c_0) {
34 + items = [];
35 + if (cond) {
36 + const foo = () => {
37 + items.push(bar());
38 + };
39 + let t0;
40 + if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
41 + t0 = () => true;
42 + $[2] = t0;
43 + } else {
44 + t0 = $[2];
45 + }
46 + const bar = t0;
47 + foo();
48 + }
49 + $[0] = cond;
50 + $[1] = items;
51 + } else {
52 + items = $[1];
53 + }
54 + return items;
55 +}
56 +
57 +export const FIXTURE_ENTRYPOINT = {
58 + fn: hoisting,
59 + params: [true],
60 + isComponent: false,
61 +};
62 +
63 +```
64 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-nested-const-declaration-2.js new
+17
@@ -0,0 +1,17 @@
1 +function hoisting(cond) {
2 + let items = [];
3 + if (cond) {
4 + const foo = () => {
5 + items.push(bar());
6 + };
7 + const bar = () => true;
8 + foo();
9 + }
10 + return items;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: hoisting,
15 + params: [true],
16 + isComponent: false,
17 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-nested-const-declaration.expect.md new
+61
@@ -0,0 +1,61 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function hoisting() {
6 + const qux = () => {
7 + let result;
8 + {
9 + result = foo();
10 + }
11 + return result;
12 + };
13 + const foo = () => {
14 + return bar + baz;
15 + };
16 + const bar = 3;
17 + const baz = 2;
18 + return qux(); // OK: called outside of TDZ
19 +}
20 +
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: hoisting,
23 + params: [],
24 + isComponent: false,
25 +};
26 +
27 +```
28 +
29 +## Code
30 +
31 +```javascript
32 +import { unstable_useMemoCache as useMemoCache } from "react";
33 +function hoisting() {
34 + const $ = useMemoCache(1);
35 + let t0;
36 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
37 + const qux = () => {
38 + const result = foo();
39 + return result;
40 + };
41 +
42 + const foo = () => bar + baz;
43 +
44 + const bar = 3;
45 + const baz = 2;
46 + t0 = qux();
47 + $[0] = t0;
48 + } else {
49 + t0 = $[0];
50 + }
51 + return t0;
52 +}
53 +
54 +export const FIXTURE_ENTRYPOINT = {
55 + fn: hoisting,
56 + params: [],
57 + isComponent: false,
58 +};
59 +
60 +```
61 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-nested-const-declaration.js new
+21
@@ -0,0 +1,21 @@
1 +function hoisting() {
2 + const qux = () => {
3 + let result;
4 + {
5 + result = foo();
6 + }
7 + return result;
8 + };
9 + const foo = () => {
10 + return bar + baz;
11 + };
12 + const bar = 3;
13 + const baz = 2;
14 + return qux(); // OK: called outside of TDZ
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: hoisting,
19 + params: [],
20 + isComponent: false,
21 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-simple-const-declaration.expect.md new
+55
@@ -0,0 +1,55 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function hoisting() {
6 + const foo = () => {
7 + return bar + baz;
8 + };
9 + const bar = 3;
10 + const baz = 2;
11 + return foo(); // OK: called outside of TDZ for bar/baz
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: hoisting,
16 + params: [],
17 + isComponent: false,
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { unstable_useMemoCache as useMemoCache } from "react";
26 +function hoisting() {
27 + const $ = useMemoCache(2);
28 + let foo;
29 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 + foo = () => bar + baz;
31 +
32 + const bar = 3;
33 + const baz = 2;
34 + $[0] = foo;
35 + } else {
36 + foo = $[0];
37 + }
38 + let t0;
39 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
40 + t0 = foo();
41 + $[1] = t0;
42 + } else {
43 + t0 = $[1];
44 + }
45 + return t0;
46 +}
47 +
48 +export const FIXTURE_ENTRYPOINT = {
49 + fn: hoisting,
50 + params: [],
51 + isComponent: false,
52 +};
53 +
54 +```
55 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-simple-const-declaration.js renamed
+1 -1
@@ -2,7 +2,7 @@ function hoisting() {
2 const foo = () => {
3 return bar + baz;
4 };
5 - let bar = 3;
5 + const bar = 3;
6 const baz = 2;
7 return foo(); // OK: called outside of TDZ for bar/baz
8 }
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-simple-function-expression.expect.md new
+57
@@ -0,0 +1,57 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function hoisting() {
6 + const foo = () => {
7 + return bar();
8 + };
9 + const bar = () => {
10 + return 1;
11 + };
12 +
13 + return foo(); // OK: bar's value is only accessed outside of its TDZ
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: hoisting,
18 + params: [],
19 + isComponent: false,
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { unstable_useMemoCache as useMemoCache } from "react";
28 +function hoisting() {
29 + const $ = useMemoCache(2);
30 + let t1;
31 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 + const foo = () => bar();
33 + let t0;
34 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
35 + t0 = () => 1;
36 + $[1] = t0;
37 + } else {
38 + t0 = $[1];
39 + }
40 + const bar = t0;
41 +
42 + t1 = foo();
43 + $[0] = t1;
44 + } else {
45 + t1 = $[0];
46 + }
47 + return t1;
48 +}
49 +
50 +export const FIXTURE_ENTRYPOINT = {
51 + fn: hoisting,
52 + params: [],
53 + isComponent: false,
54 +};
55 +
56 +```
57 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/hoisting-simple-function-expression.js renamed