mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
First-class representation of builtin jsx tags
We previously represented JsxExpressions using builtin tags - `<div>`, `<b>` etc
- by lowering the tag name to a Primitive with the string name of the tag.
However, by lowering into an independent value, it was possible that the lowered
tag name could be grouped into a different memo slot, such that we ended up with
output like:
```javascript
let t0;
if (c_1) {
...
t0 = "div"
...
} else { ... }
return <t0>{children}</t0>
```
This is obviously wrong. It's also wrong to rename `t0` -> `T0`, because React
treats that as a custom component, not a builtin. The right thing is to
explicitly model builtin components, which this PR does by making
`JsxExpression.tag` be a union of Place | BuiltinTag.
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
ArrayPattern,
|
||||
BlockId,
|
||||
BranchTerminal,
|
||||
BuiltinTag,
|
||||
Case,
|
||||
Effect,
|
||||
GeneratedSource,
|
||||
@@ -2035,7 +2036,7 @@ function lowerJsxElementName(
|
||||
exprPath: NodePath<
|
||||
t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName
|
||||
>
|
||||
): Place {
|
||||
): Place | BuiltinTag {
|
||||
const exprNode = exprPath.node;
|
||||
const exprLoc = exprNode.loc ?? GeneratedSource;
|
||||
if (exprPath.isJSXIdentifier()) {
|
||||
@@ -2047,19 +2048,11 @@ function lowerJsxElementName(
|
||||
loc: exprLoc,
|
||||
});
|
||||
} else {
|
||||
if (tag.indexOf(":") !== -1) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerJsxElementName) JSXIdentifier to have no colons, got '${tag}'`,
|
||||
severity: ErrorSeverity.InvalidInput,
|
||||
nodePath: exprPath,
|
||||
});
|
||||
}
|
||||
const place = lowerValueToTemporary(builder, {
|
||||
kind: "Primitive",
|
||||
value: tag,
|
||||
return {
|
||||
kind: "BuiltinTag",
|
||||
name: tag,
|
||||
loc: exprLoc,
|
||||
});
|
||||
return place;
|
||||
};
|
||||
}
|
||||
} else if (exprPath.isJSXMemberExpression()) {
|
||||
return lowerJsxMemberExpression(builder, exprPath);
|
||||
|
||||
@@ -608,7 +608,7 @@ export type InstructionValue =
|
||||
}
|
||||
| {
|
||||
kind: "JsxExpression";
|
||||
tag: Place;
|
||||
tag: Place | BuiltinTag;
|
||||
props: Array<JsxAttribute>;
|
||||
children: Array<Place> | null; // null === no children
|
||||
loc: SourceLocation;
|
||||
@@ -752,6 +752,12 @@ export type LoadGlobal = {
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
export type BuiltinTag = {
|
||||
kind: "BuiltinTag";
|
||||
name: string;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
/*
|
||||
* Range in which an identifier is mutable. Start and End refer to Instruction.id.
|
||||
*
|
||||
|
||||
@@ -317,18 +317,20 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
propItems.push(`...${printPlace(attribute.argument)}`);
|
||||
}
|
||||
}
|
||||
const tag =
|
||||
instrValue.tag.kind === "Identifier"
|
||||
? printPlace(instrValue.tag)
|
||||
: instrValue.tag.name;
|
||||
const props = propItems.length !== 0 ? " " + propItems.join(" ") : "";
|
||||
if (instrValue.children !== null) {
|
||||
const children = instrValue.children.map((child) => {
|
||||
return `{${printPlace(child)}}`;
|
||||
});
|
||||
value = `JSX <${printPlace(instrValue.tag)}${props}${
|
||||
value = `JSX <${tag}${props}${
|
||||
props.length > 0 ? " " : ""
|
||||
}>${children.join("")}</${printPlace(instrValue.tag)}>`;
|
||||
}>${children.join("")}</${tag}>`;
|
||||
} else {
|
||||
value = `JSX <${printPlace(instrValue.tag)}${props}${
|
||||
props.length > 0 ? " " : ""
|
||||
}/>`;
|
||||
value = `JSX <${tag}${props}${props.length > 0 ? " " : ""}/>`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,9 @@ export function* eachInstructionValueOperand(
|
||||
break;
|
||||
}
|
||||
case "JsxExpression": {
|
||||
yield instrValue.tag;
|
||||
if (instrValue.tag.kind === "Identifier") {
|
||||
yield instrValue.tag;
|
||||
}
|
||||
for (const attribute of instrValue.props) {
|
||||
switch (attribute.kind) {
|
||||
case "JsxAttribute": {
|
||||
@@ -370,7 +372,9 @@ export function mapInstructionOperands(
|
||||
break;
|
||||
}
|
||||
case "JsxExpression": {
|
||||
instrValue.tag = fn(instrValue.tag);
|
||||
if (instrValue.tag.kind === "Identifier") {
|
||||
instrValue.tag = fn(instrValue.tag);
|
||||
}
|
||||
for (const attribute of instrValue.props) {
|
||||
switch (attribute.kind) {
|
||||
case "JsxAttribute": {
|
||||
|
||||
@@ -752,7 +752,10 @@ function codegenInstructionValue(
|
||||
for (const attribute of instrValue.props) {
|
||||
attributes.push(codegenJsxAttribute(cx, attribute));
|
||||
}
|
||||
let tagValue = codegenPlace(cx, instrValue.tag);
|
||||
let tagValue =
|
||||
instrValue.tag.kind === "Identifier"
|
||||
? codegenPlace(cx, instrValue.tag)
|
||||
: t.stringLiteral(instrValue.tag.name);
|
||||
let tag: t.JSXIdentifier | t.JSXNamespacedName | t.JSXMemberExpression;
|
||||
if (tagValue.type === "Identifier") {
|
||||
tag = createJsxIdentifier(instrValue.tag.loc, tagValue.name);
|
||||
|
||||
@@ -10,9 +10,13 @@ import {
|
||||
makeInstructionId,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
ReactiveValue,
|
||||
} from "../HIR";
|
||||
import { eachInstructionValueOperand } from "../HIR/visitors";
|
||||
import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
|
||||
import {
|
||||
eachReactiveValueOperand,
|
||||
ReactiveFunctionVisitor,
|
||||
visitReactiveFunction,
|
||||
} from "./visitors";
|
||||
|
||||
/**
|
||||
* This pass supports the `fbt` translation system (https://facebook.github.io/fbt/).
|
||||
@@ -57,15 +61,14 @@ class Transform extends ReactiveFunctionVisitor<void> {
|
||||
// Record references to `fbt` as a global
|
||||
this.fbtValues.add(lvalue.identifier.id);
|
||||
} else if (
|
||||
(value.kind === "JsxExpression" &&
|
||||
this.fbtValues.has(value.tag.identifier.id)) ||
|
||||
isFbtJsxExpression(this.fbtValues, value) ||
|
||||
(value.kind === "CallExpression" &&
|
||||
this.fbtValues.has(value.callee.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)) {
|
||||
for (const operand of eachReactiveValueOperand(value)) {
|
||||
operand.identifier.scope = lvalue.identifier.scope;
|
||||
operand.identifier.mutableRange.end =
|
||||
lvalue.identifier.mutableRange.end;
|
||||
@@ -81,3 +84,15 @@ class Transform extends ReactiveFunctionVisitor<void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isFbtJsxExpression(
|
||||
fbtValues: Set<IdentifierId>,
|
||||
value: ReactiveValue
|
||||
): boolean {
|
||||
return (
|
||||
value.kind === "JsxExpression" &&
|
||||
((value.tag.kind === "Identifier" &&
|
||||
fbtValues.has(value.tag.identifier.id)) ||
|
||||
(value.tag.kind === "BuiltinTag" && value.tag.name === "fbt"))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class CollectJsxTagsVisitor extends ReactiveFunctionVisitor<JsxExpressionTags> {
|
||||
value: ReactiveValue,
|
||||
state: JsxExpressionTags
|
||||
): void {
|
||||
if (value.kind === "JsxExpression") {
|
||||
if (value.kind === "JsxExpression" && value.tag.kind === "Identifier") {
|
||||
state.add(value.tag.identifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,9 @@ function computeMemoizationInputs(
|
||||
}
|
||||
case "JsxExpression": {
|
||||
const operands: Array<Place> = [];
|
||||
operands.push(value.tag);
|
||||
if (value.tag.kind === "Identifier") {
|
||||
operands.push(value.tag);
|
||||
}
|
||||
for (const prop of value.props) {
|
||||
if (prop.kind === "JsxAttribute") {
|
||||
operands.push(prop.place);
|
||||
|
||||
+11
-15
@@ -14,27 +14,23 @@ function Component(props) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(3);
|
||||
let T0;
|
||||
let t1;
|
||||
const $ = useMemoCache(2);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
T0 = "div";
|
||||
t1 = maybeMutate(maybeMutable);
|
||||
$[0] = T0;
|
||||
t0 = maybeMutate(maybeMutable);
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
let t1;
|
||||
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = <div>{t0}</div>;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
T0 = $[0];
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <T0>{t1}</T0>;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
return t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -19,7 +19,7 @@ function Component(props) {
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $28:TObject<BuiltInArray> (frozen) (7:7)
|
||||
[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $27:TObject<BuiltInArray> (frozen) (7:7)
|
||||
```
|
||||
|
||||
|
||||
+74
-80
@@ -24,107 +24,101 @@ function Component(props) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(23);
|
||||
const $ = useMemoCache(21);
|
||||
const item = useFragment(FRAGMENT, props.item);
|
||||
useFreeze(item);
|
||||
const c_0 = $[0] !== item;
|
||||
let T1;
|
||||
let t2;
|
||||
let T3;
|
||||
let t4;
|
||||
let t1;
|
||||
let T2;
|
||||
let t3;
|
||||
let t0;
|
||||
let t5;
|
||||
let T6;
|
||||
let t7;
|
||||
let t4;
|
||||
let T5;
|
||||
let t6;
|
||||
if (c_0) {
|
||||
const count = new MaybeMutable(item);
|
||||
|
||||
T6 = View;
|
||||
t7 = "\n ";
|
||||
T3 = View;
|
||||
t4 = "\n ";
|
||||
if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
T5 = View;
|
||||
t6 = "\n ";
|
||||
T2 = View;
|
||||
t3 = "\n ";
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <span>Text</span>;
|
||||
$[9] = t0;
|
||||
$[8] = t0;
|
||||
} else {
|
||||
t0 = $[9];
|
||||
t0 = $[8];
|
||||
}
|
||||
t5 = "\n ";
|
||||
T1 = "span";
|
||||
t2 = maybeMutate(count);
|
||||
t4 = "\n ";
|
||||
t1 = maybeMutate(count);
|
||||
$[0] = item;
|
||||
$[1] = T1;
|
||||
$[2] = t2;
|
||||
$[3] = T3;
|
||||
$[4] = t4;
|
||||
$[5] = t0;
|
||||
$[6] = t5;
|
||||
$[7] = T6;
|
||||
$[8] = t7;
|
||||
$[1] = t1;
|
||||
$[2] = T2;
|
||||
$[3] = t3;
|
||||
$[4] = t0;
|
||||
$[5] = t4;
|
||||
$[6] = T5;
|
||||
$[7] = t6;
|
||||
} else {
|
||||
T1 = $[1];
|
||||
t2 = $[2];
|
||||
T3 = $[3];
|
||||
t4 = $[4];
|
||||
t0 = $[5];
|
||||
t5 = $[6];
|
||||
T6 = $[7];
|
||||
t7 = $[8];
|
||||
t1 = $[1];
|
||||
T2 = $[2];
|
||||
t3 = $[3];
|
||||
t0 = $[4];
|
||||
t4 = $[5];
|
||||
T5 = $[6];
|
||||
t6 = $[7];
|
||||
}
|
||||
const c_10 = $[10] !== T1;
|
||||
const c_11 = $[11] !== t2;
|
||||
let t8;
|
||||
if (c_10 || c_11) {
|
||||
t8 = <T1>{t2}</T1>;
|
||||
$[10] = T1;
|
||||
$[11] = t2;
|
||||
$[12] = t8;
|
||||
const c_9 = $[9] !== t1;
|
||||
let t7;
|
||||
if (c_9) {
|
||||
t7 = <span>{t1}</span>;
|
||||
$[9] = t1;
|
||||
$[10] = t7;
|
||||
} else {
|
||||
t8 = $[12];
|
||||
t7 = $[10];
|
||||
}
|
||||
const c_13 = $[13] !== T3;
|
||||
const c_11 = $[11] !== T2;
|
||||
const c_12 = $[12] !== t3;
|
||||
const c_13 = $[13] !== t0;
|
||||
const c_14 = $[14] !== t4;
|
||||
const c_15 = $[15] !== t0;
|
||||
const c_16 = $[16] !== t5;
|
||||
const c_17 = $[17] !== t8;
|
||||
let t9;
|
||||
if (c_13 || c_14 || c_15 || c_16 || c_17) {
|
||||
t9 = (
|
||||
<T3>
|
||||
{t4}
|
||||
const c_15 = $[15] !== t7;
|
||||
let t8;
|
||||
if (c_11 || c_12 || c_13 || c_14 || c_15) {
|
||||
t8 = (
|
||||
<T2>
|
||||
{t3}
|
||||
{t0}
|
||||
{t5}
|
||||
{t8}
|
||||
</T3>
|
||||
);
|
||||
$[13] = T3;
|
||||
$[14] = t4;
|
||||
$[15] = t0;
|
||||
$[16] = t5;
|
||||
$[17] = t8;
|
||||
$[18] = t9;
|
||||
} else {
|
||||
t9 = $[18];
|
||||
}
|
||||
const c_19 = $[19] !== T6;
|
||||
const c_20 = $[20] !== t7;
|
||||
const c_21 = $[21] !== t9;
|
||||
let t10;
|
||||
if (c_19 || c_20 || c_21) {
|
||||
t10 = (
|
||||
<T6>
|
||||
{t4}
|
||||
{t7}
|
||||
{t9}
|
||||
</T6>
|
||||
</T2>
|
||||
);
|
||||
$[19] = T6;
|
||||
$[20] = t7;
|
||||
$[21] = t9;
|
||||
$[22] = t10;
|
||||
$[11] = T2;
|
||||
$[12] = t3;
|
||||
$[13] = t0;
|
||||
$[14] = t4;
|
||||
$[15] = t7;
|
||||
$[16] = t8;
|
||||
} else {
|
||||
t10 = $[22];
|
||||
t8 = $[16];
|
||||
}
|
||||
return t10;
|
||||
const c_17 = $[17] !== T5;
|
||||
const c_18 = $[18] !== t6;
|
||||
const c_19 = $[19] !== t8;
|
||||
let t9;
|
||||
if (c_17 || c_18 || c_19) {
|
||||
t9 = (
|
||||
<T5>
|
||||
{t6}
|
||||
{t8}
|
||||
</T5>
|
||||
);
|
||||
$[17] = T5;
|
||||
$[18] = t6;
|
||||
$[19] = t8;
|
||||
$[20] = t9;
|
||||
} else {
|
||||
t9 = $[20];
|
||||
}
|
||||
return t9;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+49
-53
@@ -21,52 +21,62 @@ function Component(props) {
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react";
|
||||
function Component(props) {
|
||||
const $ = useMemoCache(12);
|
||||
let T1;
|
||||
let t2;
|
||||
let T3;
|
||||
let t4;
|
||||
const $ = useMemoCache(11);
|
||||
let t1;
|
||||
let T2;
|
||||
let t3;
|
||||
let t0;
|
||||
let t5;
|
||||
let T6;
|
||||
let t7;
|
||||
let t4;
|
||||
let T5;
|
||||
let t6;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
const count = new MaybeMutable();
|
||||
|
||||
T6 = View;
|
||||
t7 = "\n ";
|
||||
T3 = View;
|
||||
t4 = "\n ";
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
T5 = View;
|
||||
t6 = "\n ";
|
||||
T2 = View;
|
||||
t3 = "\n ";
|
||||
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <span>Text</span>;
|
||||
$[8] = t0;
|
||||
$[7] = t0;
|
||||
} else {
|
||||
t0 = $[8];
|
||||
t0 = $[7];
|
||||
}
|
||||
t5 = "\n ";
|
||||
T1 = "span";
|
||||
t2 = maybeMutate(count);
|
||||
$[0] = T1;
|
||||
$[1] = t2;
|
||||
$[2] = T3;
|
||||
$[3] = t4;
|
||||
$[4] = t0;
|
||||
$[5] = t5;
|
||||
$[6] = T6;
|
||||
$[7] = t7;
|
||||
t4 = "\n ";
|
||||
t1 = maybeMutate(count);
|
||||
$[0] = t1;
|
||||
$[1] = T2;
|
||||
$[2] = t3;
|
||||
$[3] = t0;
|
||||
$[4] = t4;
|
||||
$[5] = T5;
|
||||
$[6] = t6;
|
||||
} else {
|
||||
T1 = $[0];
|
||||
t2 = $[1];
|
||||
T3 = $[2];
|
||||
t4 = $[3];
|
||||
t0 = $[4];
|
||||
t5 = $[5];
|
||||
T6 = $[6];
|
||||
t7 = $[7];
|
||||
t1 = $[0];
|
||||
T2 = $[1];
|
||||
t3 = $[2];
|
||||
t0 = $[3];
|
||||
t4 = $[4];
|
||||
T5 = $[5];
|
||||
t6 = $[6];
|
||||
}
|
||||
let t7;
|
||||
if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t7 = <span>{t1}</span>;
|
||||
$[8] = t7;
|
||||
} else {
|
||||
t7 = $[8];
|
||||
}
|
||||
let t8;
|
||||
if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t8 = <T1>{t2}</T1>;
|
||||
t8 = (
|
||||
<T2>
|
||||
{t3}
|
||||
{t0}
|
||||
{t4}
|
||||
{t7}
|
||||
</T2>
|
||||
);
|
||||
$[9] = t8;
|
||||
} else {
|
||||
t8 = $[9];
|
||||
@@ -74,30 +84,16 @@ function Component(props) {
|
||||
let t9;
|
||||
if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t9 = (
|
||||
<T3>
|
||||
{t4}
|
||||
{t0}
|
||||
{t5}
|
||||
<T5>
|
||||
{t6}
|
||||
{t8}
|
||||
</T3>
|
||||
</T5>
|
||||
);
|
||||
$[10] = t9;
|
||||
} else {
|
||||
t9 = $[10];
|
||||
}
|
||||
let t10;
|
||||
if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t10 = (
|
||||
<T6>
|
||||
{t7}
|
||||
{t9}
|
||||
</T6>
|
||||
);
|
||||
$[11] = t10;
|
||||
} else {
|
||||
t10 = $[11];
|
||||
}
|
||||
return t10;
|
||||
return t9;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user