@samitouri / QOS-React-2 / commits / 9ff82c3c10

Support for (Optional)MemberExpression callee in OptionalCall

Earlier PRs bailed out when the callee of an OptionalCallExpression was a MemberExpression or OptionalMemberExpression (ie for optional method calls). This PRs expands support for optional method calls, including when the receiver, method, or both are optional. Even better, we don't need to add any additional terminals or instruction variants for this case - the one new OptionalCall terminal from earlier in the stack works for all these cases.

Joe Savona committed Mar 24, 2023 at 14:22 UTC 9ff82c3c100d27629940a27c88a4340ff008c69f
7 files changed +177 -25
compiler/forget/src/HIR/BuildHIR.ts
+54 -25
@@ -943,19 +943,6 @@ function lowerExpression(
943 const expr = exprPath as NodePath<t.OptionalCallExpression>;
944 const optional = expr.node.optional;
945 const calleePath = expr.get("callee");
946 - if (
947 - calleePath.isMemberExpression() ||
948 - calleePath.isOptionalMemberExpression()
949 - ) {
950 - // collect any errors from the arguments
951 - lowerArguments(builder, expr.get("arguments"));
952 - builder.errors.push({
953 - reason: `(BuildHIR::lowerExpression) Support OptionalCallExpression with ${calleePath.type} callee`,
954 - severity: ErrorSeverity.Todo,
955 - nodePath: calleePath,
956 - });
957 - return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
958 - }
946 const loc = expr.node.loc ?? GeneratedSource;
947 const place = buildTemporaryPlace(builder, loc);
948 const continuationBlock = builder.reserve(builder.currentBlockKind());
@@ -963,24 +950,64 @@ function lowerExpression(
950 // Lower the callee in the current block: the callee is always unconditionally evaluated
951 // The test block's branch will test on this value to determine whether to evaluate the call (consequent)
952 // or evaluate to undefined (alternate)
966 - const callee = lowerExpressionToTemporary(builder, calleePath);
953 + let callee:
954 + | { kind: "CallExpression"; callee: Place }
955 + | { kind: "MethodCall"; receiver: Place; property: Place };
956 + if (
957 + calleePath.isMemberExpression() ||
958 + calleePath.isOptionalMemberExpression()
959 + ) {
960 + const memberExpr = lowerMemberExpression(builder, calleePath);
961 + const propertyPlace = buildTemporaryPlace(builder, GeneratedSource);
962 + builder.push({
963 + id: makeInstructionId(0),
964 + lvalue: { ...propertyPlace },
965 + value: memberExpr.value,
966 + loc: GeneratedSource,
967 + });
968 + callee = {
969 + kind: "MethodCall",
970 + receiver: memberExpr.object,
971 + property: propertyPlace,
972 + };
973 + } else {
974 + callee = {
975 + kind: "CallExpression",
976 + callee: lowerExpressionToTemporary(builder, calleePath),
977 + };
978 + }
979
980 // block to evaluate if the callee is non-null/undefined. arguments are lowered in this block to preserve
981 // the semantic of conditional evaluation depending on the callee
982 const consequent = builder.enter("value", () => {
983 const args = lowerArguments(builder, expr.get("arguments"));
984 const temp = buildTemporaryPlace(builder, loc);
973 - builder.push({
974 - id: makeInstructionId(0),
975 - lvalue: { ...temp },
976 - value: {
977 - kind: "CallExpression",
978 - callee: { ...callee },
979 - args,
985 + if (callee.kind === "CallExpression") {
986 + builder.push({
987 + id: makeInstructionId(0),
988 + lvalue: { ...temp },
989 + value: {
990 + kind: "CallExpression",
991 + callee: { ...callee.callee },
992 + args,
993 + loc,
994 + },
995 loc,
981 - },
982 - loc,
983 - });
996 + });
997 + } else {
998 + builder.push({
999 + id: makeInstructionId(0),
1000 + lvalue: { ...temp },
1001 + value: {
1002 + kind: "MethodCall",
1003 + receiver: { ...callee.receiver },
1004 + property: { ...callee.property },
1005 + args,
1006 + loc: exprLoc,
1007 + },
1008 + loc,
1009 + });
1010 + }
1011 builder.push({
1012 id: makeInstructionId(0),
1013 lvalue: buildTemporaryPlace(builder, loc),
@@ -1033,9 +1060,11 @@ function lowerExpression(
1060 });
1061
1062 const testBlock = builder.enter("value", () => {
1063 + const testPlace =
1064 + callee.kind === "CallExpression" ? callee.callee : callee.property;
1065 return {
1066 kind: "branch",
1038 - test: { ...callee },
1067 + test: { ...testPlace },
1068 consequent,
1069 alternate,
1070 id: makeInstructionId(0),
compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + const x = makeObject(props);
7 + const y = makeObject(props);
8 + const z = x.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b));
9 + return z;
10 +}
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +function Component(props) {
18 + const $ = React.unstable_useMemoCache(2);
19 + const c_0 = $[0] !== props;
20 + let t0;
21 + if (c_0) {
22 + const x = makeObject(props);
23 + const y = makeObject(props);
24 + t0 = x.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b));
25 + $[0] = props;
26 + $[1] = t0;
27 + } else {
28 + t0 = $[1];
29 + }
30 + const z = t0;
31 + return z;
32 +}
33 +
34 +```
35 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/optional-method-call.js new
+6
@@ -0,0 +1,6 @@
1 +function Component(props) {
2 + const x = makeObject(props);
3 + const y = makeObject(props);
4 + const z = x.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b));
5 + return z;
6 +}
compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + const x = makeOptionalObject(props);
7 + const y = makeObject(props);
8 + const z = x?.method(y.a, props.a, foo(y.b), bar(props.b));
9 + return z;
10 +}
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +function Component(props) {
18 + const $ = React.unstable_useMemoCache(2);
19 + const c_0 = $[0] !== props;
20 + let t0;
21 + if (c_0) {
22 + const x = makeOptionalObject(props);
23 + const y = makeObject(props);
24 + t0 = x?.method(y.a, props.a, foo(y.b), bar(props.b));
25 + $[0] = props;
26 + $[1] = t0;
27 + } else {
28 + t0 = $[1];
29 + }
30 + const z = t0;
31 + return z;
32 +}
33 +
34 +```
35 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-method-call.js new
+6
@@ -0,0 +1,6 @@
1 +function Component(props) {
2 + const x = makeOptionalObject(props);
3 + const y = makeObject(props);
4 + const z = x?.method(y.a, props.a, foo(y.b), bar(props.b));
5 + return z;
6 +}
compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + const x = makeOptionalObject(props);
7 + const y = makeObject(props);
8 + const z = x?.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b));
9 + return z;
10 +}
11 +
12 +```
13 +
14 +## Code
15 +
16 +```javascript
17 +function Component(props) {
18 + const $ = React.unstable_useMemoCache(2);
19 + const c_0 = $[0] !== props;
20 + let t0;
21 + if (c_0) {
22 + const x = makeOptionalObject(props);
23 + const y = makeObject(props);
24 + t0 = x?.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b));
25 + $[0] = props;
26 + $[1] = t0;
27 + } else {
28 + t0 = $[1];
29 + }
30 + const z = t0;
31 + return z;
32 +}
33 +
34 +```
35 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/optional-receiver-optional-method.js new
+6
@@ -0,0 +1,6 @@
1 +function Component(props) {
2 + const x = makeOptionalObject(props);
3 + const y = makeObject(props);
4 + const z = x?.optionalMethod?.(y.a, props.a, foo(y.b), bar(props.b));
5 + return z;
6 +}