mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Ensure <fbt> children are not independently memod
This is a Meta-ism, but adding it for now to unblock. We special-case the `<fbt>` element for translation purposes, and have a transform that requires the children of this element to be a limited subset of nodes. Notably, any dynamic translation values must appear as `<fbt:param>` children — we disallow identifiers as children of `<fbt>` nodes. This PR adds a new pass which finds `<fbt>` nodes and ensures their immediate operands are not independently memoized. Note that this still allows the values of `<fbt:param>` to be independently memoized, as demonstrated in the unit test.
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
mergeConsecutiveBlocks,
|
||||
ReactiveFunction,
|
||||
} from "./HIR";
|
||||
import { EnvironmentConfig, Environment } from "./HIR/Environment";
|
||||
import { Environment, EnvironmentConfig } from "./HIR/Environment";
|
||||
import { validateConsistentIdentifiers } from "./HIR/ValidateConsistentIdentifiers";
|
||||
import {
|
||||
analyseFunctions,
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
flattenReactiveLoops,
|
||||
flattenScopesWithHooks,
|
||||
inferReactiveScopeVariables,
|
||||
memoizeFbtOperandsInSameScope,
|
||||
mergeOverlappingReactiveScopes,
|
||||
promoteUsedTemporaries,
|
||||
propagateScopeDependencies,
|
||||
@@ -104,6 +105,13 @@ export function* run(
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
memoizeFbtOperandsInSameScope(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
name: "MemoizeFbtOperandsInSameScope",
|
||||
value: reactiveFunction,
|
||||
});
|
||||
|
||||
alignReactiveScopesToBlockScopes(reactiveFunction);
|
||||
yield log({
|
||||
kind: "reactive",
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
IdentifierId,
|
||||
makeInstructionId,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
} from "../HIR";
|
||||
import { eachInstructionValueOperand } from "../HIR/visitors";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
|
||||
/**
|
||||
* This is a Meta-ism. We special-case the `<fbt>` element for translation purposes,
|
||||
* and have a transform that requires the children of this element to be a limited
|
||||
* subset of nodes. Notably, any dynamic translation values must appear as
|
||||
* `<fbt:param>` children — we disallow identifiers as children of `<fbt>` nodes.
|
||||
*
|
||||
* This PR adds a new pass which finds `<fbt>` nodes and ensures their immediate
|
||||
* operands are not independently memoized. Note that this still allows the values
|
||||
* of `<fbt:param>` to be independently memoized
|
||||
*/
|
||||
export function memoizeFbtOperandsInSameScope(fn: ReactiveFunction): void {
|
||||
visitReactiveFunction(fn, new Transform(), undefined);
|
||||
}
|
||||
|
||||
class Transform extends ReactiveFunctionVisitor<void> {
|
||||
fbtTags: Set<IdentifierId> = new Set();
|
||||
|
||||
override visitInstruction(
|
||||
instruction: ReactiveInstruction,
|
||||
_state: void
|
||||
): void {
|
||||
const { lvalue, value } = instruction;
|
||||
if (lvalue === null) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
value.kind === "Primitive" &&
|
||||
typeof value.value === "string" &&
|
||||
value.value === "fbt"
|
||||
) {
|
||||
// We don't distinguish between tag names and strings, so record
|
||||
// all `fbt` string literals in case they are used as a jsx tag.
|
||||
this.fbtTags.add(lvalue.identifier.id);
|
||||
} else if (
|
||||
value.kind === "JsxExpression" &&
|
||||
this.fbtTags.has(value.tag.identifier.id)
|
||||
) {
|
||||
// if the JSX element's tag was `fbt`, mark all its operands
|
||||
// to ensure that they end up in the same scope as the jsx element
|
||||
// itself.
|
||||
for (const operand of eachInstructionValueOperand(value)) {
|
||||
operand.identifier.scope = lvalue.identifier.scope;
|
||||
operand.identifier.mutableRange.end =
|
||||
lvalue.identifier.mutableRange.end;
|
||||
|
||||
// Expand the jsx element's range to account for its operands
|
||||
lvalue.identifier.mutableRange.start = makeInstructionId(
|
||||
Math.min(
|
||||
lvalue.identifier.mutableRange.start,
|
||||
operand.identifier.mutableRange.start
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export { codegenReactiveFunction } from "./CodegenReactiveFunction";
|
||||
export { flattenReactiveLoops } from "./FlattenReactiveLoops";
|
||||
export { flattenScopesWithHooks } from "./FlattenScopesWithHooks";
|
||||
export { inferReactiveScopeVariables } from "./InferReactiveScopeVariables";
|
||||
export { memoizeFbtOperandsInSameScope } from "./MemoizeFbtOperandsInSameScope";
|
||||
export { mergeOverlappingReactiveScopes } from "./MergeOverlappingReactiveScopes";
|
||||
export { printReactiveFunction } from "./PrintReactiveFunction";
|
||||
export { promoteUsedTemporaries } from "./PromoteUsedTemporaries";
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{capitalize(props.name)}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(4);
|
||||
const c_0 = $[0] !== props.name;
|
||||
let t1;
|
||||
if (c_0) {
|
||||
const c_2 = $[2] !== props.name;
|
||||
let t0;
|
||||
if (c_2) {
|
||||
t0 = capitalize(props.name);
|
||||
$[2] = props.name;
|
||||
$[3] = t0;
|
||||
} else {
|
||||
t0 = $[3];
|
||||
}
|
||||
t1 = (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello {<fbt:param name={"user name"}>{t0}</fbt:param>}
|
||||
</fbt>
|
||||
);
|
||||
$[0] = props.name;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{capitalize(props.name)}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{props.name}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(2);
|
||||
const c_0 = $[0] !== props.name;
|
||||
let t0;
|
||||
if (c_0) {
|
||||
t0 = (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello {<fbt:param name={"user name"}>{props.name}</fbt:param>}
|
||||
</fbt>
|
||||
);
|
||||
$[0] = props.name;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
function Component(props) {
|
||||
return (
|
||||
<fbt desc={"Dialog to show to user"}>
|
||||
Hello <fbt:param name="user name">{props.name}</fbt:param>
|
||||
</fbt>
|
||||
);
|
||||
}
|
||||
@@ -18,5 +18,7 @@ declare global {
|
||||
let __DEV__: boolean | null | undefined;
|
||||
}
|
||||
|
||||
console.log("loading Forget!!!");
|
||||
|
||||
import ReactForgetBabelPlugin from "./Babel/BabelPlugin";
|
||||
export default ReactForgetBabelPlugin;
|
||||
|
||||
+27
-20
@@ -17,6 +17,13 @@
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.18.6"
|
||||
|
||||
"@babel/code-frame@^7.21.4":
|
||||
version "7.21.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
|
||||
integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.18.6"
|
||||
|
||||
"@babel/compat-data@^7.19.1":
|
||||
version "7.19.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.1.tgz#72d647b4ff6a4f82878d184613353af1dd0290f9"
|
||||
@@ -83,12 +90,12 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.21.3":
|
||||
version "7.21.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce"
|
||||
integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==
|
||||
"@babel/generator@^7.21.4":
|
||||
version "7.21.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
|
||||
integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.21.3"
|
||||
"@babel/types" "^7.21.4"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
"@jridgewell/trace-mapping" "^0.3.17"
|
||||
jsesc "^2.5.1"
|
||||
@@ -274,10 +281,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3"
|
||||
integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==
|
||||
|
||||
"@babel/parser@^7.21.3":
|
||||
version "7.21.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
|
||||
integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
|
||||
"@babel/parser@^7.21.4":
|
||||
version "7.21.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
|
||||
integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==
|
||||
|
||||
"@babel/plugin-syntax-async-generators@^7.8.4":
|
||||
version "7.8.4"
|
||||
@@ -507,18 +514,18 @@
|
||||
lodash "^4.17.10"
|
||||
|
||||
"@babel/traverse@^7.19.1":
|
||||
version "7.21.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67"
|
||||
integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==
|
||||
version "7.21.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
|
||||
integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.18.6"
|
||||
"@babel/generator" "^7.21.3"
|
||||
"@babel/code-frame" "^7.21.4"
|
||||
"@babel/generator" "^7.21.4"
|
||||
"@babel/helper-environment-visitor" "^7.18.9"
|
||||
"@babel/helper-function-name" "^7.21.0"
|
||||
"@babel/helper-hoist-variables" "^7.18.6"
|
||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||
"@babel/parser" "^7.21.3"
|
||||
"@babel/types" "^7.21.3"
|
||||
"@babel/parser" "^7.21.4"
|
||||
"@babel/types" "^7.21.4"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
@@ -575,10 +582,10 @@
|
||||
"@babel/helper-validator-identifier" "^7.19.1"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.21.3":
|
||||
version "7.21.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05"
|
||||
integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==
|
||||
"@babel/types@^7.21.4":
|
||||
version "7.21.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4"
|
||||
integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.19.4"
|
||||
"@babel/helper-validator-identifier" "^7.19.1"
|
||||
|
||||
Reference in New Issue
Block a user