diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts
index 05f4ef1ae7..4ad86abbe7 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts
@@ -509,6 +509,73 @@ function evaluateInstruction(
}
return null;
}
+ case 'TemplateLiteral': {
+ if (value.subexprs.length === 0) {
+ const result: InstructionValue = {
+ kind: 'Primitive',
+ value: value.quasis.map(q => q.cooked).join(''),
+ loc: value.loc,
+ };
+ instr.value = result;
+ return result;
+ }
+
+ if (value.subexprs.length !== value.quasis.length - 1) {
+ return null;
+ }
+
+ if (value.quasis.some(q => q.cooked === undefined)) {
+ return null;
+ }
+
+ let quasiIndex = 0;
+ let resultString = value.quasis[quasiIndex].cooked as string;
+ ++quasiIndex;
+
+ for (const subExpr of value.subexprs) {
+ const subExprValue = read(constants, subExpr);
+ if (!subExprValue || subExprValue.kind !== 'Primitive') {
+ return null;
+ }
+
+ const expressionValue = subExprValue.value;
+ if (
+ typeof expressionValue !== 'number' &&
+ typeof expressionValue !== 'string' &&
+ typeof expressionValue !== 'boolean' &&
+ !(typeof expressionValue === 'object' && expressionValue === null)
+ ) {
+ // value is not supported (function, object) or invalid (symbol), or something else
+ return null;
+ }
+
+ const suffix = value.quasis[quasiIndex].cooked;
+ ++quasiIndex;
+
+ if (suffix === undefined) {
+ return null;
+ }
+
+ /*
+ * Spec states that concat calls ToString(argument) internally on its parameters
+ * -> we don't have to implement ToString(argument) ourselves and just use the engine implementation
+ * Refs:
+ * - https://tc39.es/ecma262/2024/#sec-tostring
+ * - https://tc39.es/ecma262/2024/#sec-string.prototype.concat
+ * - https://tc39.es/ecma262/2024/#sec-template-literals-runtime-semantics-evaluation
+ */
+ resultString = resultString.concat(expressionValue as string, suffix);
+ }
+
+ const result: InstructionValue = {
+ kind: 'Primitive',
+ value: resultString,
+ loc: value.loc,
+ };
+
+ instr.value = result;
+ return result;
+ }
case 'LoadLocal': {
const placeValue = read(constants, value.place);
if (placeValue !== null) {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md
new file mode 100644
index 0000000000..0f01fa0a76
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md
@@ -0,0 +1,136 @@
+
+## Input
+
+```javascript
+import {Stringify, identity} from 'shared-runtime';
+
+function foo() {
+ try {
+ identity(`${Symbol('0')}`); // Uncaught TypeError: Cannot convert a Symbol value to a string (leave as is)
+ } catch {}
+
+ return (
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: foo,
+ params: [],
+ isComponent: false,
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime";
+import { Stringify, identity } from "shared-runtime";
+
+function foo() {
+ const $ = _c(1);
+ try {
+ identity(`${Symbol("0")}`);
+ } catch {}
+ let t0;
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
+ t0 = (
+
+ );
+ $[0] = t0;
+ } else {
+ t0 = $[0];
+ }
+ return t0;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: foo,
+ params: [],
+ isComponent: false,
+};
+
+```
+
+### Eval output
+(kind: ok)
{"value":[true,true,"a\nb","\n","a1b"," abc A\n\nŧ","abc1def","abc1def2","abc1def2ghi","a4bcde6f","120","NaN","Infinity","-Infinity","9007199254740991","-9007199254740991","1.7976931348623157e+308","5e-324","0","\n ","[object Object]","1,2,3","true","false","null","undefined","1234567890","0123456789","01234567890","01234567890","0123401234567890123456789067890","012340123456789067890","0","",""]}
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js
new file mode 100644
index 0000000000..656a54db5c
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js
@@ -0,0 +1,56 @@
+import {Stringify, identity} from 'shared-runtime';
+
+function foo() {
+ try {
+ identity(`${Symbol('0')}`); // Uncaught TypeError: Cannot convert a Symbol value to a string (leave as is)
+ } catch {}
+
+ return (
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: foo,
+ params: [],
+ isComponent: false,
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md
index 49c2506045..441b6ae314 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md
@@ -40,16 +40,16 @@ function useHook(cond) {
log = [];
switch (CONST_STRING0) {
case CONST_STRING0: {
- log.push(`@A`);
+ log.push("@A");
bb0: {
if (cond) {
break bb0;
}
- log.push(`@B`);
+ log.push("@B");
}
- log.push(`@C`);
+ log.push("@C");
}
}
$[0] = cond;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md
index 36a68d07c4..4bdc7c7d92 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md
@@ -97,7 +97,7 @@ function foo(name) {
const t0 = `${name}!`;
let t1;
if ($[0] !== t0) {
- t1 = { status: ``, text: t0 };
+ t1 = { status: "", text: t0 };
$[0] = t0;
$[1] = t1;
} else {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md
index d8e991dc46..2bbfc31a11 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md
@@ -102,7 +102,7 @@ function foo(name) {
const t0 = `${name}!`;
let t1;
if ($[0] !== t0) {
- t1 = { status: ``, text: t0 };
+ t1 = { status: "", text: t0 };
$[0] = t0;
$[1] = t1;
} else {
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md
index ffdc91c8c3..cdc9738d65 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md
@@ -20,7 +20,7 @@ function componentB(props) {
```javascript
function componentA(props) {
let t = `hello ${props.a}, ${props.b}!`;
- t = t + ``;
+ t = t + "";
return t;
}
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md
index 579905a649..a990ee04e5 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md
@@ -40,14 +40,14 @@ function useHook(cond) {
log = [];
bb0: switch (CONST_STRING0) {
case CONST_STRING0: {
- log.push(`@A`);
+ log.push("@A");
if (cond) {
break bb0;
}
- log.push(`@B`);
+ log.push("@B");
- log.push(`@C`);
+ log.push("@C");
}
}
$[0] = cond;