diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 6c5b77a0b5..691ee4d6ca 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -943,19 +943,6 @@ function lowerExpression( const expr = exprPath as NodePath; const optional = expr.node.optional; const calleePath = expr.get("callee"); - if ( - calleePath.isMemberExpression() || - calleePath.isOptionalMemberExpression() - ) { - // collect any errors from the arguments - lowerArguments(builder, expr.get("arguments")); - builder.errors.push({ - reason: `(BuildHIR::lowerExpression) Support OptionalCallExpression with ${calleePath.type} callee`, - severity: ErrorSeverity.Todo, - nodePath: calleePath, - }); - return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }; - } const loc = expr.node.loc ?? GeneratedSource; const place = buildTemporaryPlace(builder, loc); const continuationBlock = builder.reserve(builder.currentBlockKind()); @@ -963,24 +950,64 @@ function lowerExpression( // Lower the callee in the current block: the callee is always unconditionally evaluated // The test block's branch will test on this value to determine whether to evaluate the call (consequent) // or evaluate to undefined (alternate) - const callee = lowerExpressionToTemporary(builder, calleePath); + let callee: + | { kind: "CallExpression"; callee: Place } + | { kind: "MethodCall"; receiver: Place; property: Place }; + if ( + calleePath.isMemberExpression() || + calleePath.isOptionalMemberExpression() + ) { + const memberExpr = lowerMemberExpression(builder, calleePath); + const propertyPlace = buildTemporaryPlace(builder, GeneratedSource); + builder.push({ + id: makeInstructionId(0), + lvalue: { ...propertyPlace }, + value: memberExpr.value, + loc: GeneratedSource, + }); + callee = { + kind: "MethodCall", + receiver: memberExpr.object, + property: propertyPlace, + }; + } else { + callee = { + kind: "CallExpression", + callee: lowerExpressionToTemporary(builder, calleePath), + }; + } // block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve // the semantic of conditional evaluation depending on the callee const consequent = builder.enter("value", () => { const args = lowerArguments(builder, expr.get("arguments")); const temp = buildTemporaryPlace(builder, loc); - builder.push({ - id: makeInstructionId(0), - lvalue: { ...temp }, - value: { - kind: "CallExpression", - callee: { ...callee }, - args, + if (callee.kind === "CallExpression") { + builder.push({ + id: makeInstructionId(0), + lvalue: { ...temp }, + value: { + kind: "CallExpression", + callee: { ...callee.callee }, + args, + loc, + }, loc, - }, - loc, - }); + }); + } else { + builder.push({ + id: makeInstructionId(0), + lvalue: { ...temp }, + value: { + kind: "MethodCall", + receiver: { ...callee.receiver }, + property: { ...callee.property }, + args, + loc: exprLoc, + }, + loc, + }); + } builder.push({ id: makeInstructionId(0), lvalue: buildTemporaryPlace(builder, loc), @@ -1033,9 +1060,11 @@ function lowerExpression( }); const testBlock = builder.enter("value", () => { + const testPlace = + callee.kind === "CallExpression" ? callee.callee : callee.property; return { kind: "branch", - test: { ...callee }, + test: { ...testPlace }, consequent, alternate, id: makeInstructionId(0), diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.expect.md new file mode 100644 index 0000000000..7b0c0ec820 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +function Component(props) { + const x = makeObject(props); + const y = makeObject(props); + const z = x.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b)); + return z; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props; + let t0; + if (c_0) { + const x = makeObject(props); + const y = makeObject(props); + t0 = x.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b)); + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + const z = t0; + return z; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.js b/compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.js new file mode 100644 index 0000000000..25145d61ed --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.js @@ -0,0 +1,6 @@ +function Component(props) { + const x = makeObject(props); + const y = makeObject(props); + const z = x.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b)); + return z; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.expect.md new file mode 100644 index 0000000000..942abb774e --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +function Component(props) { + const x = makeOptionalObject(props); + const y = makeObject(props); + const z = x?.method(y.a, props.a, foo(y.b), bar(props.b)); + return z; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props; + let t0; + if (c_0) { + const x = makeOptionalObject(props); + const y = makeObject(props); + t0 = x?.method(y.a, props.a, foo(y.b), bar(props.b)); + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + const z = t0; + return z; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.js b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.js new file mode 100644 index 0000000000..698437dcd8 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.js @@ -0,0 +1,6 @@ +function Component(props) { + const x = makeOptionalObject(props); + const y = makeObject(props); + const z = x?.method(y.a, props.a, foo(y.b), bar(props.b)); + return z; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.expect.md new file mode 100644 index 0000000000..bca8669825 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +function Component(props) { + const x = makeOptionalObject(props); + const y = makeObject(props); + const z = x?.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b)); + return z; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props; + let t0; + if (c_0) { + const x = makeOptionalObject(props); + const y = makeObject(props); + t0 = x?.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b)); + $[0] = props; + $[1] = t0; + } else { + t0 = $[1]; + } + const z = t0; + return z; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.js b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.js new file mode 100644 index 0000000000..0d62a3c78d --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.js @@ -0,0 +1,6 @@ +function Component(props) { + const x = makeOptionalObject(props); + const y = makeObject(props); + const z = x?.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b)); + return z; +}