Repository: facebook/react
Pull request: [compiler] Always emit FuncDecl for outlined functions

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom):
* #30466
* #30465
* __->__ #30464

Addresses follow up feedback from #30446. Since the outlined function is
guaranteed to have a module-scoped unique identifier name, we can
simplify the insertion logic for the outlined function to always emit a
function declaration rather than switch based on the original function
type. This is fine because the outlined function is synthetic anyway.

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
patch: @@ -195,7 +195,7 @@ export function createNewFunctionNode(
   return transformedFn;
 }
 
-function insertNewFunctionNode(
+function insertNewOutlinedFunctionNode(
   originalFn: BabelFn,
   compiledFn: CodegenFunction,
 ): NodePath<t.Function> {
@@ -206,30 +206,16 @@ function insertNewFunctionNode(
       )[0]!;
     }
     /**
-     * This is pretty gross but we can't just append an (Arrow)FunctionExpression as a sibling of
-     * the original function. If the original function was itself an (Arrow)FunctionExpression,
-     * this would cause its parent to become a SequenceExpression instead which breaks a bunch of
-     * assumptions elsewhere in the plugin.
+     * We can't just append the outlined function as a sibling of the original function if it is an
+     * (Arrow)FunctionExpression parented by a VariableDeclaration, as this would cause its parent
+     * to become a SequenceExpression instead which breaks a bunch of assumptions elsewhere in the
+     * plugin.
      *
-     * To get around this, we synthesize a new VariableDeclaration holding the compiled function
-     * expression and insert it as a true sibling (ie within the Program's block statements).
+     * To get around this, we always synthesize a new FunctionDeclaration for the outlined function
+     * and insert it as a true sibling to the original function.
      */
     case 'ArrowFunctionExpression':
     case 'FunctionExpression': {
-      const funcExpr = createNewFunctionNode(originalFn, compiledFn);
-      CompilerError.invariant(
-        t.isArrowFunctionExpression(funcExpr) ||
-          t.isFunctionExpression(funcExpr),
-        {
-          reason: 'Expected an (arrow) function expression to be created',
-          description: `Got: ${funcExpr.type}`,
-          loc: funcExpr.loc ?? null,
-        },
-      );
-      CompilerError.invariant(compiledFn.id != null, {
-        reason: 'Expected compiled function to have an identifier',
-        loc: compiledFn.loc,
-      });
       CompilerError.invariant(
         originalFn.parentPath.isVariableDeclarator() &&
           originalFn.parentPath.parentPath.isVariableDeclaration(),
@@ -238,23 +224,23 @@ function insertNewFunctionNode(
           loc: originalFn.node.loc ?? null,
         },
       );
+      const fn: t.FunctionDeclaration = {
+        type: 'FunctionDeclaration',
+        id: compiledFn.id,
+        loc: originalFn.node.loc ?? null,
+        async: compiledFn.async,
+        generator: compiledFn.generator,
+        params: compiledFn.params,
+        body: compiledFn.body,
+      };
       const varDecl = originalFn.parentPath.parentPath;
-      varDecl.insertAfter(
-        t.variableDeclaration('const', [
-          t.variableDeclarator(compiledFn.id, funcExpr),
-        ]),
-      );
-      const insertedFuncExpr = varDecl.get('declarations')[0]!.get('init')!;
-      CompilerError.invariant(
-        insertedFuncExpr.isArrowFunctionExpression() ||
-          insertedFuncExpr.isFunctionExpression(),
-        {
-          reason: 'Expected inserted (arrow) function expression',
-          description: `Got: ${insertedFuncExpr}`,
-          loc: insertedFuncExpr.node?.loc ?? null,
-        },
-      );
-      return insertedFuncExpr;
+      const insertedFuncDecl = varDecl.insertAfter(fn)[0]!;
+      CompilerError.invariant(insertedFuncDecl.isFunctionDeclaration(), {
+        reason: 'Expected inserted function declaration',
+        description: `Got: ${insertedFuncDecl}`,
+        loc: insertedFuncDecl.node?.loc ?? null,
+      });
+      return insertedFuncDecl;
     }
     default: {
       assertExhaustive(
@@ -482,7 +468,7 @@ export function compileProgram(
         reason: 'Unexpected nested outlined functions',
         loc: outlined.fn.loc,
       });
-      const fn = insertNewFunctionNode(current.fn, outlined.fn);
+      const fn = insertNewOutlinedFunctionNode(current.fn, outlined.fn);
       fn.skip();
       ALREADY_COMPILED.add(fn.node);
       if (outlined.type !== null) {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlining-in-func-expr.expect.md
patch: @@ -50,9 +50,9 @@ const Component2 = (props) => {
   }
   return t1;
 };
-const _temp = (item) => {
+function _temp(item) {
   return <li key={item.id}>{item.name}</li>;
-};
+}
 
 export const FIXTURE_ENTRYPOINT = {
   fn: Component2,