Repository: facebook/react
Pull request: [compiler] Store original and new prop names

Previously, we'd directly store the original attributes from the jsx expressions. But this isn't enough as we want to rename duplicate attributes.

This PR refactors the prop collection logic to store both the original and new names for jsx attributes in the newly outlined jsx expression.

For now, both the new and old names are the same. In the future, they will be different when we add support for outlining expressions with duplicate attribute names.

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts
patch: @@ -210,10 +210,16 @@ function process(
   return {instrs: newInstrs, fn: outlinedFn};
 }
 
+type OutlinedJsxAttribute = {
+  originalName: string;
+  newName: string;
+  place: Place;
+};
+
 function collectProps(
   instructions: Array<JsxInstruction>,
-): Array<JsxAttribute> | null {
-  const attributes: Array<JsxAttribute> = [];
+): Array<OutlinedJsxAttribute> | null {
+  const attributes: Array<OutlinedJsxAttribute> = [];
   const jsxIds = new Set(instructions.map(i => i.lvalue.identifier.id));
   const seen: Set<string> = new Set();
   for (const instr of instructions) {
@@ -234,7 +240,11 @@ function collectProps(
 
       if (at.kind === 'JsxAttribute') {
         seen.add(at.name);
-        attributes.push(at);
+        attributes.push({
+          originalName: at.name,
+          newName: at.name,
+          place: at.place,
+        });
       }
     }
 
@@ -252,9 +262,15 @@ function collectProps(
 function emitOutlinedJsx(
   env: Environment,
   instructions: Array<Instruction>,
-  props: Array<JsxAttribute>,
+  outlinedProps: Array<OutlinedJsxAttribute>,
   outlinedTag: string,
 ): Array<Instruction> {
+  const props: Array<JsxAttribute> = outlinedProps.map(p => ({
+    kind: 'JsxAttribute',
+    name: p.newName,
+    place: p.place,
+  }));
+
   const loadJsx: Instruction = {
     id: makeInstructionId(0),
     loc: GeneratedSource,
@@ -290,7 +306,7 @@ function emitOutlinedJsx(
 function emitOutlinedFn(
   env: Environment,
   jsx: Array<JsxInstruction>,
-  oldProps: Array<JsxAttribute>,
+  oldProps: Array<OutlinedJsxAttribute>,
   globals: LoadGlobalMap,
 ): HIRFunction | null {
   const instructions: Array<Instruction> = [];
@@ -299,9 +315,11 @@ function emitOutlinedFn(
   const propsObj: Place = createTemporaryPlace(env, GeneratedSource);
   promoteTemporary(propsObj.identifier);
 
-  const destructurePropsInstr = emitDestructureProps(env, propsObj, [
-    ...oldToNewProps.values(),
-  ]);
+  const destructurePropsInstr = emitDestructureProps(
+    env,
+    propsObj,
+    oldToNewProps,
+  );
   instructions.push(destructurePropsInstr);
 
   const updatedJsxInstructions = emitUpdatedJsx(jsx, oldToNewProps);
@@ -368,7 +386,7 @@ function emitLoadGlobals(
 
 function emitUpdatedJsx(
   jsx: Array<JsxInstruction>,
-  oldToNewProps: Map<IdentifierId, ObjectProperty>,
+  oldToNewProps: Map<IdentifierId, OutlinedJsxAttribute>,
 ): Array<JsxInstruction> {
   const newInstrs: Array<JsxInstruction> = [];
 
@@ -390,7 +408,8 @@ function emitUpdatedJsx(
         `Expected a new property for ${printIdentifier(prop.place.identifier)}`,
       );
       newProps.push({
-        ...prop,
+        kind: 'JsxAttribute',
+        name: newProp.originalName,
         place: newProp.place,
       });
     }
@@ -409,31 +428,21 @@ function emitUpdatedJsx(
 
 function createOldToNewPropsMapping(
   env: Environment,
-  oldProps: Array<JsxAttribute>,
-): Map<IdentifierId, ObjectProperty> {
+  oldProps: Array<OutlinedJsxAttribute>,
+): Map<IdentifierId, OutlinedJsxAttribute> {
   const oldToNewProps = new Map();
 
   for (const oldProp of oldProps) {
-    invariant(
-      oldProp.kind === 'JsxAttribute',
-      `Expected only attributes but found ${oldProp.kind}`,
-    );
-
     // Do not read key prop in the outlined component
-    if (oldProp.name === 'key') {
+    if (oldProp.originalName === 'key') {
       continue;
     }
 
-    const newProp: ObjectProperty = {
-      kind: 'ObjectProperty',
-      key: {
-        kind: 'string',
-        name: oldProp.name,
-      },
-      type: 'property',
+    const newProp: OutlinedJsxAttribute = {
+      ...oldProp,
       place: createTemporaryPlace(env, GeneratedSource),
     };
-    newProp.place.identifier.name = makeIdentifierName(oldProp.name);
+    newProp.place.identifier.name = makeIdentifierName(oldProp.newName);
     oldToNewProps.set(oldProp.place.identifier.id, newProp);
   }
 
@@ -443,8 +452,21 @@ function createOldToNewPropsMapping(
 function emitDestructureProps(
   env: Environment,
   propsObj: Place,
-  properties: Array<ObjectProperty>,
+  oldToNewProps: Map<IdentifierId, OutlinedJsxAttribute>,
 ): Instruction {
+  const properties: Array<ObjectProperty> = [];
+  for (const [_, prop] of oldToNewProps) {
+    properties.push({
+      kind: 'ObjectProperty',
+      key: {
+        kind: 'string',
+        name: prop.newName,
+      },
+      type: 'property',
+      place: prop.place,
+    });
+  }
+
   const destructurePropsInstr: Instruction = {
     id: makeInstructionId(0),
     lvalue: createTemporaryPlace(env, GeneratedSource),