mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Generalize helper for reorderable expressions
Adds a new helper method that we can use when processing expressions whose evaluation ordering may not be preserved. This was previously the case only for switch test case values, but we can use this for AssignmentPattern (destructuring default values) as well.
This commit is contained in:
@@ -514,51 +514,7 @@ function lowerStatement(
|
||||
});
|
||||
let test: Place | null = null;
|
||||
if (testExpr.node != null) {
|
||||
switch (testExpr.node.type) {
|
||||
case "Identifier":
|
||||
case "StringLiteral":
|
||||
case "NumericLiteral":
|
||||
case "NullLiteral":
|
||||
case "BooleanLiteral":
|
||||
case "BigIntLiteral": {
|
||||
// ok
|
||||
break;
|
||||
}
|
||||
case "MemberExpression": {
|
||||
// A common pattern is switch statements where the case test values are properties of a global,
|
||||
// eg `case ProductOptions.Option: { ... }`
|
||||
// We therefore allow expressions where the innermost object is a global identifier, and reject
|
||||
// all other member expressions (for now).
|
||||
const test = testExpr as NodePath<t.MemberExpression>;
|
||||
let innerObject: NodePath<t.Expression> = test;
|
||||
while (innerObject.isMemberExpression()) {
|
||||
innerObject = innerObject.get("object");
|
||||
}
|
||||
if (
|
||||
innerObject.isIdentifier() &&
|
||||
builder.resolveIdentifier(innerObject) === null // null means global
|
||||
) {
|
||||
// This is a property/computed load from a global, that's safe to evaluate as a test expression
|
||||
break;
|
||||
}
|
||||
builder.errors.push({
|
||||
reason:
|
||||
"(BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported",
|
||||
severity: ErrorSeverity.Todo,
|
||||
nodePath: testExpr,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
builder.errors.push({
|
||||
reason:
|
||||
"(BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported",
|
||||
severity: ErrorSeverity.Todo,
|
||||
nodePath: testExpr,
|
||||
});
|
||||
}
|
||||
}
|
||||
test = lowerExpressionToTemporary(
|
||||
test = lowerReorderableExpression(
|
||||
builder,
|
||||
testExpr as NodePath<t.Expression>
|
||||
);
|
||||
@@ -1830,6 +1786,88 @@ function lowerExpression(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There are a few places where we do not preserve original evaluation ordering, such as switch case test values
|
||||
* and default values in destructuring (assignment patterns). In these cases we allow simple expressions whose
|
||||
* evaluation cannot be observed: primitives and arrays/objects whose values are also safely reorderable.
|
||||
*/
|
||||
function lowerReorderableExpression(
|
||||
builder: HIRBuilder,
|
||||
expr: NodePath<t.Expression>
|
||||
): Place {
|
||||
if (isReorderableExpression(builder, expr)) {
|
||||
return lowerExpressionToTemporary(builder, expr);
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::node.lowerReorderableExpression) Expression type '${expr.type}' cannot be safely reordered`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
nodePath: expr,
|
||||
});
|
||||
return buildTemporaryPlace(builder, expr.node.loc ?? GeneratedSource);
|
||||
}
|
||||
}
|
||||
|
||||
function isReorderableExpression(
|
||||
builder: HIRBuilder,
|
||||
expr: NodePath<t.Expression>
|
||||
): boolean {
|
||||
switch (expr.node.type) {
|
||||
case "Identifier":
|
||||
case "RegExpLiteral":
|
||||
case "StringLiteral":
|
||||
case "NumericLiteral":
|
||||
case "NullLiteral":
|
||||
case "BooleanLiteral":
|
||||
case "BigIntLiteral": {
|
||||
return true;
|
||||
}
|
||||
case "ArrayExpression": {
|
||||
return (expr as NodePath<t.ArrayExpression>)
|
||||
.get("elements")
|
||||
.every(
|
||||
(element) =>
|
||||
element.isExpression() && isReorderableExpression(builder, element)
|
||||
);
|
||||
}
|
||||
case "ObjectExpression": {
|
||||
return (expr as NodePath<t.ObjectExpression>)
|
||||
.get("properties")
|
||||
.every((property) => {
|
||||
if (!property.isObjectProperty() || property.node.computed) {
|
||||
return false;
|
||||
}
|
||||
const value = property.get("value");
|
||||
return (
|
||||
value.isExpression() && isReorderableExpression(builder, value)
|
||||
);
|
||||
});
|
||||
}
|
||||
case "MemberExpression": {
|
||||
// A common pattern is switch statements where the case test values are properties of a global,
|
||||
// eg `case ProductOptions.Option: { ... }`
|
||||
// We therefore allow expressions where the innermost object is a global identifier, and reject
|
||||
// all other member expressions (for now).
|
||||
const test = expr as NodePath<t.MemberExpression>;
|
||||
let innerObject: NodePath<t.Expression> = test;
|
||||
while (innerObject.isMemberExpression()) {
|
||||
innerObject = innerObject.get("object");
|
||||
}
|
||||
if (
|
||||
innerObject.isIdentifier() &&
|
||||
builder.resolveIdentifier(innerObject) === null // null means global
|
||||
) {
|
||||
// This is a property/computed load from a global, that's safe to reorder
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lowerArguments(
|
||||
builder: HIRBuilder,
|
||||
expr: Array<
|
||||
@@ -2455,7 +2493,9 @@ function lowerAssignment(
|
||||
const continuationBlock = builder.reserve(builder.currentBlockKind());
|
||||
|
||||
const consequent = builder.enter("value", () => {
|
||||
const defaultValue = lowerExpressionToTemporary(
|
||||
// Because we reorder evaluation, we restrict the allowed default values to those where
|
||||
// evaluation order is unobservable
|
||||
const defaultValue = lowerReorderableExpression(
|
||||
builder,
|
||||
lvalue.get("right")
|
||||
);
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const [[x] = [foo()]] = props.y;
|
||||
const [[x] = ["default"]] = props.y;
|
||||
return x;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ function Component(props) {
|
||||
const c_0 = $[0] !== t0;
|
||||
let t1;
|
||||
if (c_0) {
|
||||
t1 = t0 === undefined ? [foo()] : t0;
|
||||
t1 = t0 === undefined ? ["default"] : t0;
|
||||
$[0] = t0;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
function Component(props) {
|
||||
const [[x] = [foo()]] = props.y;
|
||||
const [[x] = ["default"]] = props.y;
|
||||
return x;
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
function Component(props) {
|
||||
let x;
|
||||
if (props.cond) {
|
||||
[[x] = [foo()]] = props.y;
|
||||
[[x] = ["default"]] = props.y;
|
||||
} else {
|
||||
x = props.fallback;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ function Component(props) {
|
||||
const c_0 = $[0] !== t0;
|
||||
let t1;
|
||||
if (c_0) {
|
||||
t1 = t0 === undefined ? [foo()] : t0;
|
||||
t1 = t0 === undefined ? ["default"] : t0;
|
||||
$[0] = t0;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
function Component(props) {
|
||||
let x;
|
||||
if (props.cond) {
|
||||
[[x] = [foo()]] = props.y;
|
||||
[[x] = ["default"]] = props.y;
|
||||
} else {
|
||||
x = props.fallback;
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ let moduleLocal = false;
|
||||
48 | switch (i) {
|
||||
49 | case 1 + 1: {
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
|
||||
[ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'MemberExpression' cannot be safely reordered
|
||||
51 | case foo(): {
|
||||
52 | }
|
||||
> 53 | case x.y: {
|
||||
@@ -273,7 +273,7 @@ let moduleLocal = false;
|
||||
55 | default: {
|
||||
56 | }
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
|
||||
[ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'CallExpression' cannot be safely reordered
|
||||
49 | case 1 + 1: {
|
||||
50 | }
|
||||
> 51 | case foo(): {
|
||||
@@ -282,7 +282,7 @@ let moduleLocal = false;
|
||||
53 | case x.y: {
|
||||
54 | }
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported
|
||||
[ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'BinaryExpression' cannot be safely reordered
|
||||
47 |
|
||||
48 | switch (i) {
|
||||
> 49 | case 1 + 1: {
|
||||
|
||||
Reference in New Issue
Block a user