mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
feat(compiler): Implement constant propagation for template literals (#33139)
New take on #29716 ## Summary 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" // now true ``` If a template string literal can only partially be comptime-evaluated, it is not that useful for dead code elimination or further constant folding steps and thus, is left as-is in that case. Same is true if the literal contains an array, object, symbol or function. ## How did you test this change? See added tests.
This commit is contained in:
parent
38ef6550a8
commit
ac06829246
|
|
@ -509,6 +509,73 @@ function evaluateInstruction(
|
||||||
}
|
}
|
||||||
return null;
|
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': {
|
case 'LoadLocal': {
|
||||||
const placeValue = read(constants, value.place);
|
const placeValue = read(constants, value.place);
|
||||||
if (placeValue !== null) {
|
if (placeValue !== null) {
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
<Stringify
|
||||||
|
value={[
|
||||||
|
`` === '',
|
||||||
|
`\n` === '\n',
|
||||||
|
`a\nb`,
|
||||||
|
`\n`,
|
||||||
|
`a${1}b`,
|
||||||
|
` abc \u0041\n\u000a\ŧ`,
|
||||||
|
`abc${1}def`,
|
||||||
|
`abc${1}def${2}`,
|
||||||
|
`abc${1}def${2}ghi`,
|
||||||
|
`a${1 + 3}b${``}c${'d' + `e${2 + 4}f`}`,
|
||||||
|
`1${2}${Math.sin(0)}`,
|
||||||
|
`${NaN}`,
|
||||||
|
`${Infinity}`,
|
||||||
|
`${-Infinity}`,
|
||||||
|
`${Number.MAX_SAFE_INTEGER}`,
|
||||||
|
`${Number.MIN_SAFE_INTEGER}`,
|
||||||
|
`${Number.MAX_VALUE}`,
|
||||||
|
`${Number.MIN_VALUE}`,
|
||||||
|
`${-0}`,
|
||||||
|
`
|
||||||
|
`,
|
||||||
|
`${{}}`,
|
||||||
|
`${[1, 2, 3]}`,
|
||||||
|
`${true}`,
|
||||||
|
`${false}`,
|
||||||
|
`${null}`,
|
||||||
|
`${undefined}`,
|
||||||
|
`123456789${0}`,
|
||||||
|
`${0}123456789`,
|
||||||
|
`${0}123456789${0}`,
|
||||||
|
`${0}1234${5}6789${0}`,
|
||||||
|
`${0}1234${`${0}123456789${`${0}123456789${0}`}`}6789${0}`,
|
||||||
|
`${0}1234${`${0}123456789${`${identity(0)}`}`}6789${0}`,
|
||||||
|
`${`${`${`${0}`}`}`}`,
|
||||||
|
`${`${`${`${''}`}`}`}`,
|
||||||
|
`${`${`${`${identity('')}`}`}`}`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (
|
||||||
|
<Stringify
|
||||||
|
value={[
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
|
||||||
|
"a\nb",
|
||||||
|
"\n",
|
||||||
|
"a1b",
|
||||||
|
" abc A\n\n\u0167",
|
||||||
|
"abc1def",
|
||||||
|
"abc1def2",
|
||||||
|
"abc1def2ghi",
|
||||||
|
"a4bcde6f",
|
||||||
|
`1${2}${Math.sin(0)}`,
|
||||||
|
`${NaN}`,
|
||||||
|
`${Infinity}`,
|
||||||
|
`${-Infinity}`,
|
||||||
|
`${Number.MAX_SAFE_INTEGER}`,
|
||||||
|
`${Number.MIN_SAFE_INTEGER}`,
|
||||||
|
`${Number.MAX_VALUE}`,
|
||||||
|
`${Number.MIN_VALUE}`,
|
||||||
|
"0",
|
||||||
|
"\n ",
|
||||||
|
|
||||||
|
`${{}}`,
|
||||||
|
`${[1, 2, 3]}`,
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"null",
|
||||||
|
`${undefined}`,
|
||||||
|
"1234567890",
|
||||||
|
"0123456789",
|
||||||
|
"01234567890",
|
||||||
|
"01234567890",
|
||||||
|
"0123401234567890123456789067890",
|
||||||
|
`${0}1234${`${0}123456789${`${identity(0)}`}`}6789${0}`,
|
||||||
|
"0",
|
||||||
|
"",
|
||||||
|
`${`${`${`${identity("")}`}`}`}`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
$[0] = t0;
|
||||||
|
} else {
|
||||||
|
t0 = $[0];
|
||||||
|
}
|
||||||
|
return t0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: foo,
|
||||||
|
params: [],
|
||||||
|
isComponent: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eval output
|
||||||
|
(kind: ok) <div>{"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","",""]}</div>
|
||||||
|
|
@ -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 (
|
||||||
|
<Stringify
|
||||||
|
value={[
|
||||||
|
`` === '',
|
||||||
|
`\n` === '\n',
|
||||||
|
`a\nb`,
|
||||||
|
`\n`,
|
||||||
|
`a${1}b`,
|
||||||
|
` abc \u0041\n\u000a\ŧ`,
|
||||||
|
`abc${1}def`,
|
||||||
|
`abc${1}def${2}`,
|
||||||
|
`abc${1}def${2}ghi`,
|
||||||
|
`a${1 + 3}b${``}c${'d' + `e${2 + 4}f`}`,
|
||||||
|
`1${2}${Math.sin(0)}`,
|
||||||
|
`${NaN}`,
|
||||||
|
`${Infinity}`,
|
||||||
|
`${-Infinity}`,
|
||||||
|
`${Number.MAX_SAFE_INTEGER}`,
|
||||||
|
`${Number.MIN_SAFE_INTEGER}`,
|
||||||
|
`${Number.MAX_VALUE}`,
|
||||||
|
`${Number.MIN_VALUE}`,
|
||||||
|
`${-0}`,
|
||||||
|
`
|
||||||
|
`,
|
||||||
|
`${{}}`,
|
||||||
|
`${[1, 2, 3]}`,
|
||||||
|
`${true}`,
|
||||||
|
`${false}`,
|
||||||
|
`${null}`,
|
||||||
|
`${undefined}`,
|
||||||
|
`123456789${0}`,
|
||||||
|
`${0}123456789`,
|
||||||
|
`${0}123456789${0}`,
|
||||||
|
`${0}1234${5}6789${0}`,
|
||||||
|
`${0}1234${`${0}123456789${`${0}123456789${0}`}`}6789${0}`,
|
||||||
|
`${0}1234${`${0}123456789${`${identity(0)}`}`}6789${0}`,
|
||||||
|
`${`${`${`${0}`}`}`}`,
|
||||||
|
`${`${`${`${''}`}`}`}`,
|
||||||
|
`${`${`${`${identity('')}`}`}`}`,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FIXTURE_ENTRYPOINT = {
|
||||||
|
fn: foo,
|
||||||
|
params: [],
|
||||||
|
isComponent: false,
|
||||||
|
};
|
||||||
|
|
@ -40,16 +40,16 @@ function useHook(cond) {
|
||||||
log = [];
|
log = [];
|
||||||
switch (CONST_STRING0) {
|
switch (CONST_STRING0) {
|
||||||
case CONST_STRING0: {
|
case CONST_STRING0: {
|
||||||
log.push(`@A`);
|
log.push("@A");
|
||||||
bb0: {
|
bb0: {
|
||||||
if (cond) {
|
if (cond) {
|
||||||
break bb0;
|
break bb0;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.push(`@B`);
|
log.push("@B");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.push(`@C`);
|
log.push("@C");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$[0] = cond;
|
$[0] = cond;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ function foo(name) {
|
||||||
const t0 = `${name}!`;
|
const t0 = `${name}!`;
|
||||||
let t1;
|
let t1;
|
||||||
if ($[0] !== t0) {
|
if ($[0] !== t0) {
|
||||||
t1 = { status: `<status>`, text: t0 };
|
t1 = { status: "<status>", text: t0 };
|
||||||
$[0] = t0;
|
$[0] = t0;
|
||||||
$[1] = t1;
|
$[1] = t1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ function foo(name) {
|
||||||
const t0 = `${name}!`;
|
const t0 = `${name}!`;
|
||||||
let t1;
|
let t1;
|
||||||
if ($[0] !== t0) {
|
if ($[0] !== t0) {
|
||||||
t1 = { status: `<status>`, text: t0 };
|
t1 = { status: "<status>", text: t0 };
|
||||||
$[0] = t0;
|
$[0] = t0;
|
||||||
$[1] = t1;
|
$[1] = t1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ function componentB(props) {
|
||||||
```javascript
|
```javascript
|
||||||
function componentA(props) {
|
function componentA(props) {
|
||||||
let t = `hello ${props.a}, ${props.b}!`;
|
let t = `hello ${props.a}, ${props.b}!`;
|
||||||
t = t + ``;
|
t = t + "";
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,14 +40,14 @@ function useHook(cond) {
|
||||||
log = [];
|
log = [];
|
||||||
bb0: switch (CONST_STRING0) {
|
bb0: switch (CONST_STRING0) {
|
||||||
case CONST_STRING0: {
|
case CONST_STRING0: {
|
||||||
log.push(`@A`);
|
log.push("@A");
|
||||||
if (cond) {
|
if (cond) {
|
||||||
break bb0;
|
break bb0;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.push(`@B`);
|
log.push("@B");
|
||||||
|
|
||||||
log.push(`@C`);
|
log.push("@C");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$[0] = cond;
|
$[0] = cond;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user