Repository: facebook/react
Pull request: [compiler] Prepare HIRBuilder to be used by later passes

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32286).
* #33326
* #33325
* __->__ #32286

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
patch: @@ -130,6 +130,7 @@ function run(
     mode,
     config,
     contextIdentifiers,
+    func,
     logger,
     filename,
     code,

filename: compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
patch: @@ -70,12 +70,14 @@ import {BuiltInArrayId} from './ObjectShape';
 export function lower(
   func: NodePath<t.Function>,
   env: Environment,
+  // Bindings captured from the outer function, in case lower() is called recursively (for lambdas)
   bindings: Bindings | null = null,
   capturedRefs: Array<t.Identifier> = [],
-  // the outermost function being compiled, in case lower() is called recursively (for lambdas)
-  parent: NodePath<t.Function> | null = null,
 ): Result<HIRFunction, CompilerError> {
-  const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
+  const builder = new HIRBuilder(env, {
+    bindings,
+    context: capturedRefs,
+  });
   const context: HIRFunction['context'] = [];
 
   for (const ref of capturedRefs ?? []) {
@@ -215,7 +217,7 @@ export function lower(
   return Ok({
     id,
     params,
-    fnType: parent == null ? env.fnType : 'Other',
+    fnType: bindings == null ? env.fnType : 'Other',
     returnTypeAnnotation: null, // TODO: extract the actual return type node if present
     returnType: makeType(),
     body: builder.build(),
@@ -3417,7 +3419,7 @@ function lowerFunction(
     | t.ObjectMethod
   >,
 ): LoweredFunction | null {
-  const componentScope: Scope = builder.parentFunction.scope;
+  const componentScope: Scope = builder.environment.parentFunction.scope;
   const capturedContext = gatherCapturedContext(expr, componentScope);
 
   /*
@@ -3428,13 +3430,10 @@ function lowerFunction(
    * This isn't a problem in practice because use Babel's scope analysis to
    * identify the correct references.
    */
-  const lowering = lower(
-    expr,
-    builder.environment,
-    builder.bindings,
-    [...builder.context, ...capturedContext],
-    builder.parentFunction,
-  );
+  const lowering = lower(expr, builder.environment, builder.bindings, [
+    ...builder.context,
+    ...capturedContext,
+  ]);
   let loweredFunc: HIRFunction;
   if (lowering.isErr()) {
     lowering
@@ -3456,7 +3455,7 @@ function lowerExpressionToTemporary(
   return lowerValueToTemporary(builder, value);
 }
 
-function lowerValueToTemporary(
+export function lowerValueToTemporary(
   builder: HIRBuilder,
   value: InstructionValue,
 ): Place {

filename: compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
patch: @@ -47,7 +47,7 @@ import {
   ShapeRegistry,
   addHook,
 } from './ObjectShape';
-import {Scope as BabelScope} from '@babel/traverse';
+import {Scope as BabelScope, NodePath} from '@babel/traverse';
 import {TypeSchema} from './TypeSchema';
 
 export const ReactElementSymbolSchema = z.object({
@@ -675,13 +675,15 @@ export class Environment {
 
   #contextIdentifiers: Set<t.Identifier>;
   #hoistedIdentifiers: Set<t.Identifier>;
+  parentFunction: NodePath<t.Function>;
 
   constructor(
     scope: BabelScope,
     fnType: ReactFunctionType,
     compilerMode: CompilerMode,
     config: EnvironmentConfig,
     contextIdentifiers: Set<t.Identifier>,
+    parentFunction: NodePath<t.Function>, // the outermost function being compiled
     logger: Logger | null,
     filename: string | null,
     code: string | null,
@@ -740,6 +742,7 @@ export class Environment {
       this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType);
     }
 
+    this.parentFunction = parentFunction;
     this.#contextIdentifiers = contextIdentifiers;
     this.#hoistedIdentifiers = new Set();
   }

filename: compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts
patch: @@ -110,7 +110,6 @@ export default class HIRBuilder {
   #bindings: Bindings;
   #env: Environment;
   #exceptionHandlerStack: Array<BlockId> = [];
-  parentFunction: NodePath<t.Function>;
   errors: CompilerError = new CompilerError();
   /**
    * Traversal context: counts the number of `fbt` tag parents
@@ -136,16 +135,17 @@ export default class HIRBuilder {
 
   constructor(
     env: Environment,
-    parentFunction: NodePath<t.Function>, // the outermost function being compiled
-    bindings: Bindings | null = null,
-    context: Array<t.Identifier> | null = null,
+    options?: {
+      bindings?: Bindings | null;
+      context?: Array<t.Identifier>;
+      entryBlockKind?: BlockKind;
+    },
   ) {
     this.#env = env;
-    this.#bindings = bindings ?? new Map();
-    this.parentFunction = parentFunction;
-    this.#context = context ?? [];
+    this.#bindings = options?.bindings ?? new Map();
+    this.#context = options?.context ?? [];
     this.#entry = makeBlockId(env.nextBlockId);
-    this.#current = newBlock(this.#entry, 'block');
+    this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
   }
 
   currentBlockKind(): BlockKind {
@@ -239,7 +239,7 @@ export default class HIRBuilder {
 
     // Check if the binding is from module scope
     const outerBinding =
-      this.parentFunction.scope.parent.getBinding(originalName);
+      this.#env.parentFunction.scope.parent.getBinding(originalName);
     if (babelBinding === outerBinding) {
       const path = babelBinding.path;
       if (path.isImportDefaultSpecifier()) {
@@ -293,7 +293,7 @@ export default class HIRBuilder {
     const binding = this.#resolveBabelBinding(path);
     if (binding) {
       // Check if the binding is from module scope, if so return null
-      const outerBinding = this.parentFunction.scope.parent.getBinding(
+      const outerBinding = this.#env.parentFunction.scope.parent.getBinding(
         path.node.name,
       );
       if (binding === outerBinding) {
@@ -376,7 +376,7 @@ export default class HIRBuilder {
   }
 
   // Terminate the current block w the given terminal, and start a new block
-  terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
+  terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId {
     const {id: blockId, kind, instructions} = this.#current;
     this.#completed.set(blockId, {
       kind,
@@ -390,6 +390,7 @@ export default class HIRBuilder {
       const nextId = this.#env.nextBlockId;
       this.#current = newBlock(nextId, nextBlockKind);
     }
+    return blockId;
   }
 
   /*
@@ -746,6 +747,11 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
      * (eg bb2 then bb1), we ensure that they get reversed back to the correct order.
      */
     const block = func.blocks.get(blockId)!;
+    CompilerError.invariant(block != null, {
+      reason: '[HIRBuilder] Unexpected null block',
+      description: `expected block ${blockId} to exist`,
+      loc: GeneratedSource,
+    });
     const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
     const fallthrough = terminalFallthrough(block.terminal);