Skip to content

Commit f28ead6

Browse files
committed
feat(compiler): Implement constant propagation for template literals
Template literals consisting entirely of constant values will be inlined to a string literal, effectively replacing the backticks with a double quote. This is done primarily to make the resulting instruction a string literal, so it can be processed further in constant propatation. So this is now correctly simplified to `true`: ```js `` === "" // now true `a${1}` === "a1" ``` If a template literal contains a non-constant value, the entire literal is left as-is. Transforming a template literal to one that has only partially inlined is something that can be improved upon in the future.
1 parent d77dd31 commit f28ead6

8 files changed

+147
-9
lines changed

compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,52 @@ function evaluateInstruction(
472472
}
473473
return null;
474474
}
475+
case "TemplateLiteral": {
476+
if (value.quasis.some((q) => q.cooked === undefined)) {
477+
return null;
478+
}
479+
480+
if (value.subexprs.length === 0) {
481+
const result: InstructionValue = {
482+
kind: "Primitive",
483+
value: value.quasis.map((q) => q.cooked).join(""),
484+
loc: value.loc,
485+
};
486+
instr.value = result;
487+
return result;
488+
}
489+
490+
const subExpressionValues = value.subexprs.map((subExpr) =>
491+
read(constants, subExpr)
492+
);
493+
494+
let resultString = value.quasis[0].cooked;
495+
let quasiIndex = 1;
496+
for (const subExprValue of subExpressionValues) {
497+
if (!subExprValue) {
498+
return null;
499+
}
500+
if (subExprValue.kind !== "Primitive") {
501+
return null;
502+
}
503+
if (subExprValue.value === null || subExprValue.value === undefined) {
504+
return null;
505+
}
506+
507+
const suffix = value.quasis[quasiIndex].cooked;
508+
resultString += subExprValue.value.toString() + suffix;
509+
++quasiIndex;
510+
}
511+
512+
const result: InstructionValue = {
513+
kind: "Primitive",
514+
value: resultString,
515+
loc: value.loc,
516+
};
517+
518+
instr.value = result;
519+
return result;
520+
}
475521
case "PropertyLoad": {
476522
const objectValue = read(constants, value.object);
477523
if (objectValue !== null) {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
## Input
3+
4+
```javascript
5+
import { Stringify } from "shared-runtime";
6+
7+
function foo() {
8+
return <Stringify value={{
9+
a: `` === "",
10+
b: `a${1}b`,
11+
c: ` abc \u0041\n\u000a\ŧ`,
12+
d: `abc${1}def`,
13+
e: `abc${1}def${2}`,
14+
f: `abc${1}def${2}ghi`,
15+
g: `a${1 + 3}b${``}c${"d" + `e${2 + 4}f`}`,
16+
h: `1${2}${Math.sin(0)}`,
17+
}} />
18+
}
19+
20+
export const FIXTURE_ENTRYPOINT = {
21+
fn: foo,
22+
params: [],
23+
isComponent: false,
24+
};
25+
26+
```
27+
28+
## Code
29+
30+
```javascript
31+
import { c as _c } from "react/compiler-runtime";
32+
import { Stringify } from "shared-runtime";
33+
34+
function foo() {
35+
const $ = _c(1);
36+
let t0;
37+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
38+
t0 = (
39+
<Stringify
40+
value={{
41+
a: true,
42+
b: "a1b",
43+
c: " abc A\n\n\u0167",
44+
d: "abc1def",
45+
e: "abc1def2",
46+
f: "abc1def2ghi",
47+
g: "a4bcde6f",
48+
h: `1${2}${Math.sin(0)}`,
49+
}}
50+
/>
51+
);
52+
$[0] = t0;
53+
} else {
54+
t0 = $[0];
55+
}
56+
return t0;
57+
}
58+
59+
export const FIXTURE_ENTRYPOINT = {
60+
fn: foo,
61+
params: [],
62+
isComponent: false,
63+
};
64+
65+
```
66+
67+
### Eval output
68+
(kind: ok) <div>{"value":{"a":true,"b":"a1b","c":" abc A\n\nŧ","d":"abc1def","e":"abc1def2","f":"abc1def2ghi","g":"a4bcde6f","h":"120"}}</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Stringify } from "shared-runtime";
2+
3+
function foo() {
4+
return (
5+
<Stringify
6+
value={{
7+
a: `` === "",
8+
b: `a${1}b`,
9+
c: ` abc \u0041\n\u000a\ŧ`,
10+
d: `abc${1}def`,
11+
e: `abc${1}def${2}`,
12+
f: `abc${1}def${2}ghi`,
13+
g: `a${1 + 3}b${``}c${"d" + `e${2 + 4}f`}`,
14+
h: `1${2}${Math.sin(0)}`,
15+
}}
16+
/>
17+
);
18+
}
19+
20+
export const FIXTURE_ENTRYPOINT = {
21+
fn: foo,
22+
params: [],
23+
isComponent: false,
24+
};

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,16 @@ function useHook(cond) {
4040
log = [];
4141
switch (CONST_STRING0) {
4242
case CONST_STRING0: {
43-
log.push(`@A`);
43+
log.push("@A");
4444
bb0: {
4545
if (cond) {
4646
break bb0;
4747
}
4848

49-
log.push(`@B`);
49+
log.push("@B");
5050
}
5151

52-
log.push(`@C`);
52+
log.push("@C");
5353
}
5454
}
5555
$[0] = cond;

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function foo(name) {
9797
const t0 = `${name}!`;
9898
let t1;
9999
if ($[0] !== t0) {
100-
t1 = { status: `<status>`, text: t0 };
100+
t1 = { status: "<status>", text: t0 };
101101
$[0] = t0;
102102
$[1] = t1;
103103
} else {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function foo(name) {
102102
const t0 = `${name}!`;
103103
let t1;
104104
if ($[0] !== t0) {
105-
t1 = { status: `<status>`, text: t0 };
105+
t1 = { status: "<status>", text: t0 };
106106
$[0] = t0;
107107
$[1] = t1;
108108
} else {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function componentB(props) {
2020
```javascript
2121
function componentA(props) {
2222
let t = `hello ${props.a}, ${props.b}!`;
23-
t = t + ``;
23+
t = t + "";
2424
return t;
2525
}
2626

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ function useHook(cond) {
4040
log = [];
4141
bb0: switch (CONST_STRING0) {
4242
case CONST_STRING0: {
43-
log.push(`@A`);
43+
log.push("@A");
4444
if (cond) {
4545
break bb0;
4646
}
4747

48-
log.push(`@B`);
48+
log.push("@B");
4949

50-
log.push(`@C`);
50+
log.push("@C");
5151
}
5252
}
5353
$[0] = cond;

0 commit comments

Comments
 (0)