[hir] Disallow unconditional load from optional memberexpr

This commit is contained in:
Mofei Zhang
2023-03-30 19:23:23 -04:00
parent c00e7a2af2
commit c77c1b1644
7 changed files with 94 additions and 23 deletions
+41 -13
View File
@@ -1796,39 +1796,67 @@ function lowerMemberExpression(
): { object: Place; property: Place | string; value: InstructionValue } {
const exprNode = expr.node;
const exprLoc = exprNode.loc ?? GeneratedSource;
const object = lowerExpressionToTemporary(builder, expr.get("object"));
const property = expr.get("property");
const objectNode = expr.get("object");
const propertyNode = expr.get("property");
const object = lowerExpressionToTemporary(builder, objectNode);
if (
objectNode.isOptionalMemberExpression() &&
!expr.isOptionalMemberExpression()
) {
// Babel's `isOptionalMemberExpression` indicates whether this property load itself
// is conditional (i.e. within an "optional chain"). This is different from the
// `optional` property, which is only true for property loads at the start of an
// optional chain. e.g. `a.b?.c.d` decomposes into
// [0] MemberExpr a.b; // not in an optional chain
// [1] OptionalMemberExpr [0]?.c
// [2] OptionalMemberExpr [1].c
// [3] OptionalMemberExpr [2].d
// We currently do not handle non-conditional loads from an optional memberexpr
// e.g. `(a?.b).c`
// See error.nonoptional-load-from-optional-memberexpr test fixture for details
builder.errors.push({
reason: `(BuildHIR::lowerMemberExpression) Handle optional chaining for non-optional member expr.`,
severity: ErrorSeverity.Todo,
nodePath: propertyNode,
});
return {
object,
property: propertyNode.toString(),
value: { kind: "UnsupportedNode", node: exprNode, loc: exprLoc },
};
}
if (!expr.node.computed) {
if (!property.isIdentifier()) {
if (!propertyNode.isIdentifier()) {
builder.errors.push({
reason: `(BuildHIR::lowerMemberExpression) Handle ${property.type} property`,
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
severity: ErrorSeverity.Todo,
nodePath: property,
nodePath: propertyNode,
});
return {
object,
property: property.toString(),
property: propertyNode.toString(),
value: { kind: "UnsupportedNode", node: exprNode, loc: exprLoc },
};
}
const value: InstructionValue = {
kind: "PropertyLoad",
object: { ...object },
property: property.node.name,
property: propertyNode.node.name,
loc: exprLoc,
optional: expr.node.optional ?? false,
};
return { object, property: property.node.name, value };
return { object, property: propertyNode.node.name, value };
} else {
if (!property.isExpression()) {
if (!propertyNode.isExpression()) {
builder.errors.push({
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${property.type} property`,
reason: `(BuildHIR::lowerMemberExpression) Expected Expression, got ${propertyNode.type} property`,
severity: ErrorSeverity.InvalidInput,
nodePath: property,
nodePath: propertyNode,
});
return {
object,
property: property.toString(),
property: propertyNode.toString(),
value: {
kind: "UnsupportedNode",
node: exprNode,
@@ -1843,7 +1871,7 @@ function lowerMemberExpression(
nodePath: expr,
});
}
const propertyPlace = lowerExpressionToTemporary(builder, property);
const propertyPlace = lowerExpressionToTemporary(builder, propertyNode);
const value: InstructionValue = {
kind: "ComputedLoad",
object: { ...object },
@@ -752,17 +752,22 @@ function codegenInstructionValue(
break;
}
case "PropertyLoad": {
if (instrValue.optional) {
const object = codegenPlace(cx, instrValue.object);
// We currently only lower single chains of optional memberexpr.
// (See BuildHIR.ts for more detail.)
if (t.isOptionalMemberExpression(object) || instrValue.optional) {
value = t.optionalMemberExpression(
codegenPlace(cx, instrValue.object),
object,
t.identifier(instrValue.property),
undefined,
true
instrValue.optional
);
} else {
value = t.memberExpression(
codegenPlace(cx, instrValue.object),
t.identifier(instrValue.property)
object,
t.identifier(instrValue.property),
undefined,
instrValue.optional
);
}
break;
@@ -0,0 +1,30 @@
## Input
```javascript
// Note that `a?.b.c` is semantically different from `(a?.b).c`
// Here, 'props?.a` is an optional chain, and `.b` is an unconditional load
// (nullthrows if a is nullish)
function Component(props) {
let x = (props?.a).b;
return x;
}
```
## Error
```
[ReactForget] TodoError: (BuildHIR::lowerMemberExpression) Handle optional chaining for non-optional member expr.
4 |
5 | function Component(props) {
> 6 | let x = (props?.a).b;
| ^
7 | return x;
8 | }
9 |
```
@@ -0,0 +1,8 @@
// Note that `a?.b.c` is semantically different from `(a?.b).c`
// Here, 'props?.a` is an optional chain, and `.b` is an unconditional load
// (nullthrows if a is nullish)
function Component(props) {
let x = (props?.a).b;
return x;
}
@@ -21,7 +21,7 @@ function Component(props) {
const c_0 = $[0] !== props.a;
let t0;
if (c_0) {
t0 = foo((props.a?.b).c.d);
t0 = foo(props.a?.b.c.d);
$[0] = props.a;
$[1] = t0;
} else {
@@ -6,7 +6,7 @@
// We should codegen the correct member expressions
function Component(props) {
let x = props?.b.c;
let y = (props?.x).y;
let y = props?.b.c.d?.e.f.g?.h;
return { x, y };
}
@@ -19,8 +19,8 @@ function Component(props) {
// We should codegen the correct member expressions
function Component(props) {
const $ = React.unstable_useMemoCache(3);
const x = (props?.b).c;
const y = (props?.x).y;
const x = props?.b.c;
const y = props?.b.c.d?.e.f.g?.h;
const c_0 = $[0] !== x;
const c_1 = $[1] !== y;
let t0;
@@ -2,6 +2,6 @@
// We should codegen the correct member expressions
function Component(props) {
let x = props?.b.c;
let y = (props?.x).y;
let y = props?.b.c.d?.e.f.g?.h;
return { x, y };
}