diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 253817287d..62e023b757 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -1680,9 +1680,11 @@ 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. + * There are a few places where we do not preserve original evaluation ordering and/or control flow, 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 + * - arrays/objects whose values are also safely reorderable. */ function lowerReorderableExpression( builder: HIRBuilder, @@ -1864,21 +1866,28 @@ function lowerMemberExpression( }, }; } - if (t.isOptionalMemberExpression(expr)) { - builder.errors.push({ - reason: `(BuildHIR::lowerMemberExpression) Handle computed OptionalMemberExpression`, - severity: ErrorSeverity.Todo, - nodePath: expr, - }); + let optional; + let property: Place; + + // See "PropertyLoad" for the difference between optionalMemberExpr() + // and node.optional here + if (expr.isOptionalMemberExpression()) { + // if expr is in an optional chain, evaluation of `property` is + // conditional on whether expr is nullish + property = lowerReorderableExpression(builder, propertyNode); + optional = expr.node.optional ?? false; + } else { + property = lowerExpressionToTemporary(builder, propertyNode); + optional = false; } - const propertyPlace = lowerExpressionToTemporary(builder, propertyNode); const value: InstructionValue = { kind: "ComputedLoad", object: { ...object }, - property: { ...propertyPlace }, + property: { ...property }, loc: exprLoc, + optional, }; - return { object, property: propertyPlace, value }; + return { object, property, value }; } } diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index ef873fd87a..ea2d29f657 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -609,6 +609,7 @@ export type InstructionValue = kind: "ComputedLoad"; object: Place; property: Place; + optional: boolean; loc: SourceLocation; } // `delete object[property]` diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index 8b82b77a59..32f658a93d 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -367,9 +367,9 @@ export function printInstructionValue(instrValue: ReactiveValue): string { break; } case "ComputedLoad": { - value = `ComputedLoad ${printPlace(instrValue.object)}[${printPlace( - instrValue.property - )}]`; + value = `ComputedLoad ${printPlace(instrValue.object)}${ + instrValue.optional ? "?" : "" + }[${printPlace(instrValue.property)}]`; break; } case "ComputedStore": { diff --git a/compiler/forget/src/Optimization/ConstantPropagation.ts b/compiler/forget/src/Optimization/ConstantPropagation.ts index b5db60a784..4e9803fa57 100644 --- a/compiler/forget/src/Optimization/ConstantPropagation.ts +++ b/compiler/forget/src/Optimization/ConstantPropagation.ts @@ -160,7 +160,7 @@ function evaluateInstruction( loc: value.loc, property: property.value, object: value.object, - optional: false, + optional: value.optional, }; // Future-proofing: when we add support for optional computed properties, // we'll need to copy the value here diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 0a8681a95f..7c65b8ebfd 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -795,11 +795,18 @@ function codegenInstructionValue( break; } case "ComputedLoad": { - value = t.memberExpression( - codegenPlace(cx, instrValue.object), - codegenPlace(cx, instrValue.property), - true - ); + const object = codegenPlace(cx, instrValue.object); + const property = codegenPlace(cx, instrValue.property); + if (t.isOptionalMemberExpression(object) || instrValue.optional) { + value = t.optionalMemberExpression( + object, + property, + true, + instrValue.optional + ); + } else { + value = t.memberExpression(object, property, true, instrValue.optional); + } break; } case "ComputedDelete": { diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.optional-computed-member-expression.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.optional-computed-member-expression.expect.md index 126823579a..403dd693d4 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/error.optional-computed-member-expression.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.optional-computed-member-expression.expect.md @@ -13,11 +13,11 @@ function Component(props) { ## Error ``` -[ReactForget] TodoError: (BuildHIR::lowerMemberExpression) Handle computed OptionalMemberExpression +[ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'MemberExpression' cannot be safely reordered 1 | function Component(props) { 2 | const object = makeObject(props); > 3 | return object?.[props.key]; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^ 4 | } 5 | ``` diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-computed-load-static.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/optional-computed-load-static.expect.md new file mode 100644 index 0000000000..cd520d13e1 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-computed-load-static.expect.md @@ -0,0 +1,21 @@ + +## Input + +```javascript +function Component(props) { + let x = a?.b.c[0]; + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const x = a?.b.c[0]; + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-computed-load-static.js b/compiler/forget/src/__tests__/fixtures/compiler/optional-computed-load-static.js new file mode 100644 index 0000000000..aa192bc82d --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-computed-load-static.js @@ -0,0 +1,4 @@ +function Component(props) { + let x = a?.b.c[0]; + return x; +}