@samitouri / QOS-React-2 / commits / feb8e924e4

BuildHIR for OptionalCallExpression

Implements lowering for a subset of optional calls - specifically, we don't (yet) support when the callee is a member expression or an optional member expression. So `foo?.()` works but we bailout on `object?.foo()` and `object.foo?.()`. For `<calleee>?.(<args>)` we lower as roughly: ``` bb0: t0 = <callee> OptionalCall test=bb1 fallthrough= bb1 (value): Branch t0 consequent=bb2 alternate=bb3 bb2 (value): ...lower <args> here... t1 = Call t0, args StoreLocal res, t1 Goto bb4 bb3 (value): t2 = undefined StoreLocal res, t2 Goto bb4 bb4: // result in `res` here ```

Joe Savona committed Mar 24, 2023 at 14:22 UTC feb8e924e49a9b3a6cfaab12c75f6eb3835e0e9d
3 files changed +112 -3
compiler/forget/src/HIR/BuildHIR.ts
+107 -3
@@ -11,7 +11,6 @@ import { Expression } from "@babel/types";
11 import invariant from "invariant";
12 import { CompilerError, ErrorSeverity } from "../CompilerError";
13 import { Err, Ok, Result } from "../Utils/Result";
14 -import todo from "../Utils/todo";
14 import { assertExhaustive } from "../Utils/utils";
15 import { Environment, EnvironmentOptions } from "./Environment";
16 import {
@@ -942,9 +941,114 @@ function lowerExpression(
941 }
942 case "OptionalCallExpression": {
943 const expr = exprPath as NodePath<t.OptionalCallExpression>;
945 - const _optional = expr.get("optional");
944 + const optional = expr.get("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 + }
959 + const loc = expr.node.loc ?? GeneratedSource;
960 + const place = buildTemporaryPlace(builder, loc);
961 + const continuationBlock = builder.reserve(builder.currentBlockKind());
962 +
963 + const callee = lowerExpressionToTemporary(builder, calleePath);
964
947 - return todo("OptionalCallExpression");
965 + const consequent = builder.enter("value", () => {
966 + const args = lowerArguments(builder, expr.get("arguments"));
967 + const temp = buildTemporaryPlace(builder, loc);
968 + builder.push({
969 + id: makeInstructionId(0),
970 + lvalue: { ...temp },
971 + value: {
972 + kind: "CallExpression",
973 + callee: { ...callee },
974 + args,
975 + loc,
976 + },
977 + loc,
978 + });
979 + builder.push({
980 + id: makeInstructionId(0),
981 + lvalue: buildTemporaryPlace(builder, loc),
982 + value: {
983 + kind: "StoreLocal",
984 + lvalue: { kind: InstructionKind.Const, place: { ...place } },
985 + value: { ...temp },
986 + loc,
987 + },
988 + loc,
989 + });
990 + return {
991 + kind: "goto",
992 + variant: GotoVariant.Break,
993 + block: continuationBlock.id,
994 + id: makeInstructionId(0),
995 + };
996 + });
997 + const alternate = builder.enter("value", () => {
998 + const temp = buildTemporaryPlace(builder, loc);
999 + builder.push({
1000 + id: makeInstructionId(0),
1001 + lvalue: { ...temp },
1002 + value: {
1003 + kind: "Primitive",
1004 + value: undefined,
1005 + loc,
1006 + },
1007 + loc,
1008 + });
1009 + builder.push({
1010 + id: makeInstructionId(0),
1011 + lvalue: buildTemporaryPlace(builder, loc),
1012 + value: {
1013 + kind: "StoreLocal",
1014 + lvalue: { kind: InstructionKind.Const, place: { ...place } },
1015 + value: { ...temp },
1016 + loc,
1017 + },
1018 + loc,
1019 + });
1020 + return {
1021 + kind: "goto",
1022 + variant: GotoVariant.Break,
1023 + block: continuationBlock.id,
1024 + id: makeInstructionId(0),
1025 + };
1026 + });
1027 +
1028 + const testBlock = builder.enter("value", () => {
1029 + return {
1030 + kind: "branch",
1031 + test: { ...callee },
1032 + consequent,
1033 + alternate,
1034 + id: makeInstructionId(0),
1035 + loc,
1036 + };
1037 + });
1038 +
1039 + builder.terminateWithContinuation(
1040 + {
1041 + kind: "optional-call",
1042 + optional,
1043 + test: testBlock,
1044 + fallthrough: continuationBlock.id,
1045 + id: makeInstructionId(0),
1046 + loc,
1047 + },
1048 + continuationBlock
1049 + );
1050 +
1051 + return { kind: "LoadLocal", place, loc: place.loc };
1052 }
1053 case "CallExpression": {
1054 const expr = exprPath as NodePath<t.CallExpression>;
compiler/forget/src/HIR/HIR.ts
+4
@@ -355,6 +355,10 @@ export type TernaryTerminal = {
355
356 export type OptionalCallTerminal = {
357 kind: "optional-call";
358 + // Whether the call itself is optional. If false, this means that the callee itself
359 + // is optionally evaluated (has some OptionalMemberExpresion) but the final call is
360 + // required (if the callee is not null/undefined).
361 + optional: boolean;
362 test: BlockId;
363 fallthrough: BlockId;
364 id: InstructionId;
compiler/forget/src/HIR/visitors.ts
+1
@@ -567,6 +567,7 @@ export function mapTerminalSuccessors(
567 const fallthrough = fn(terminal.fallthrough);
568 return {
569 kind: "optional-call",
570 + optional: terminal.optional,
571 test,
572 fallthrough,
573 id: makeInstructionId(0),