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

Collect bailouts in BuildHIR::lowerExpression

Mostly mechanical changes to lowerExpression to push an error to HIRBuilder rather than invariant.

Lauren Tan committed Jan 20, 2023 at 11:47 UTC b2983571f284c31f74a8c34d37db9e7bd492a5e2
3 files changed +396 -160
compiler/forget/src/HIR/BuildHIR.ts
+258 -127
@@ -811,84 +811,140 @@ function lowerExpression(
811 const expr = exprPath as NodePath<t.ObjectExpression>;
812 const propertyPaths = expr.get("properties");
813 const properties: Map<string, Place> = new Map();
814 + let hasError = false;
815 for (const propertyPath of propertyPaths) {
815 - todoInvariant(
816 - propertyPath.isObjectProperty(),
817 - "Handle object property spread"
818 - );
816 + if (!propertyPath.isObjectProperty()) {
817 + builder.pushError({
818 + reason: "Handle object property spread",
819 + severity: ErrorSeverity.Todo,
820 + nodePath: propertyPath,
821 + });
822 + hasError = true;
823 + continue;
824 + }
825 const key = propertyPath.node.key;
820 - invariant(key.type === "Identifier", "Unexpected private name");
826 + if (key.type !== "Identifier") {
827 + builder.pushError({
828 + reason: "Unexpected private name",
829 + severity: ErrorSeverity.InvalidInput,
830 + nodePath: propertyPath,
831 + });
832 + hasError = true;
833 + continue;
834 + }
835 const valuePath = propertyPath.get("value");
822 - todoInvariant(
823 - valuePath.isExpression(),
824 - "Handle non-expression object values"
825 - );
836 + if (!valuePath.isExpression()) {
837 + builder.pushError({
838 + reason: "Handle non-expression object values",
839 + severity: ErrorSeverity.Todo,
840 + nodePath: valuePath,
841 + });
842 + hasError = true;
843 + continue;
844 + }
845 const value = lowerExpressionToPlace(builder, valuePath);
846 properties.set(key.name, value);
847 }
829 - return {
830 - kind: "ObjectExpression",
831 - properties,
832 - loc: exprLoc,
833 - };
848 + return hasError
849 + ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
850 + : {
851 + kind: "ObjectExpression",
852 + properties,
853 + loc: exprLoc,
854 + };
855 }
856 case "ArrayExpression": {
857 const expr = exprPath as NodePath<t.ArrayExpression>;
837 - const elements = expr.get("elements").map((element) => {
838 - todoInvariant(
839 - element.hasNode() && element.isExpression(),
840 - "todo: handle non-expression array elements"
841 - );
842 - return lowerExpressionToPlace(builder, element);
843 - });
844 - return {
845 - kind: "ArrayExpression",
846 - elements,
847 - loc: exprLoc,
848 - };
858 + let hasError = false;
859 + let elements: Place[] = [];
860 + for (const element of expr.get("elements")) {
861 + if (!element.hasNode() || !element.isExpression()) {
862 + builder.pushError({
863 + reason: "Handle non-expression array elements",
864 + severity: ErrorSeverity.Todo,
865 + nodePath: element,
866 + });
867 + hasError = true;
868 + continue;
869 + }
870 + elements.push(lowerExpressionToPlace(builder, element));
871 + }
872 + return hasError
873 + ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
874 + : {
875 + kind: "ArrayExpression",
876 + elements,
877 + loc: exprLoc,
878 + };
879 }
880 case "NewExpression": {
881 const expr = exprPath as NodePath<t.NewExpression>;
882 const calleePath = expr.get("callee");
853 - invariant(
854 - calleePath.isExpression(),
855 - "Call expressions only support callees that are expressions (v8 intrinsics not supported)"
856 - );
883 + if (!calleePath.isExpression()) {
884 + builder.pushError({
885 + reason:
886 + "Call expressions only support callees that are expressions (v8 intrinsics not supported)",
887 + severity: ErrorSeverity.InvalidInput,
888 + nodePath: calleePath,
889 + });
890 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
891 + }
892 const callee = lowerExpressionToPlace(builder, calleePath);
858 - const argPaths = expr.get("arguments");
859 - const args = argPaths.map((arg) => {
860 - todoInvariant(
861 - arg.isExpression(),
862 - "todo: support non-expression call arguments"
863 - );
864 - return lowerExpressionToPlace(builder, arg);
865 - });
866 - return {
867 - kind: "NewExpression",
868 - callee,
869 - args,
870 - loc: exprLoc,
871 - };
893 + let args: Place[] = [];
894 + let hasError = false;
895 + for (const argPath of expr.get("arguments")) {
896 + if (!argPath.isExpression()) {
897 + builder.pushError({
898 + reason: "Support non-expression arguments to NewExpression",
899 + severity: ErrorSeverity.Todo,
900 + nodePath: argPath,
901 + });
902 + hasError = true;
903 + continue;
904 + }
905 + args.push(lowerExpressionToPlace(builder, argPath));
906 + }
907 +
908 + return hasError
909 + ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
910 + : {
911 + kind: "NewExpression",
912 + callee,
913 + args,
914 + loc: exprLoc,
915 + };
916 }
917 case "CallExpression": {
918 const expr = exprPath as NodePath<t.CallExpression>;
919 const calleePath = expr.get("callee");
876 - invariant(
877 - calleePath.isExpression(),
878 - "Call expressions only support callees that are expressions (v8 intrinsics not supported)"
879 - );
920 + let hasError = false;
921 + if (!calleePath.isExpression()) {
922 + builder.pushError({
923 + reason:
924 + "Call expressions only support callees that are expressions (v8 intrinsics not supported)",
925 + severity: ErrorSeverity.InvalidInput,
926 + nodePath: calleePath,
927 + });
928 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
929 + }
930 if (calleePath.isMemberExpression()) {
931 const { object, property, value } = lowerMemberExpression(
932 builder,
933 calleePath
934 );
885 - const args = expr.get("arguments").map((arg) => {
886 - todoInvariant(
887 - arg.isExpression(),
888 - "todo: support non-expression call arguments"
889 - );
890 - return lowerExpressionToPlace(builder, arg);
891 - });
935 + let args: Place[] = [];
936 + for (const argPath of expr.get("arguments")) {
937 + if (!argPath.isExpression()) {
938 + builder.pushError({
939 + reason: "Support non-expression arguments to CallExpression",
940 + severity: ErrorSeverity.Todo,
941 + nodePath: argPath,
942 + });
943 + hasError = true;
944 + continue;
945 + }
946 + args.push(lowerExpressionToPlace(builder, argPath));
947 + }
948 if (typeof property === "string") {
949 return {
950 kind: "PropertyCall",
@@ -908,28 +964,41 @@ function lowerExpression(
964 }
965 } else {
966 const callee = lowerExpressionToPlace(builder, calleePath);
911 - const args = expr.get("arguments").map((arg) => {
912 - todoInvariant(
913 - arg.isExpression(),
914 - "todo: support non-expression call arguments"
915 - );
916 - return lowerExpressionToPlace(builder, arg);
917 - });
918 - return {
919 - kind: "CallExpression",
920 - callee,
921 - args,
922 - loc: exprLoc,
923 - };
967 + let args: Place[] = [];
968 + for (const argPath of expr.get("arguments")) {
969 + if (!argPath.isExpression()) {
970 + builder.pushError({
971 + reason: "Support non-expression arguments to CallExpression",
972 + severity: ErrorSeverity.Todo,
973 + nodePath: argPath,
974 + });
975 + hasError = true;
976 + continue;
977 + }
978 + args.push(lowerExpressionToPlace(builder, argPath));
979 + }
980 + return hasError
981 + ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
982 + : {
983 + kind: "CallExpression",
984 + callee,
985 + args,
986 + loc: exprLoc,
987 + };
988 }
989 }
990 case "BinaryExpression": {
991 const expr = exprPath as NodePath<t.BinaryExpression>;
992 const leftPath = expr.get("left");
929 - invariant(
930 - leftPath.isExpression(),
931 - "Private names may not appear as the left hand side of a binary expression"
932 - );
993 + if (!leftPath.isExpression()) {
994 + builder.pushError({
995 + reason:
996 + "Private names may not appear as the left hand side of a binary expression",
997 + severity: ErrorSeverity.InvalidInput,
998 + nodePath: leftPath,
999 + });
1000 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1001 + }
1002 const left = lowerExpressionToPlace(builder, leftPath);
1003 const right = lowerExpressionToPlace(builder, expr.get("right"));
1004 const operator = expr.node.operator;
@@ -944,10 +1013,15 @@ function lowerExpression(
1013 case "LogicalExpression": {
1014 const expr = exprPath as NodePath<t.LogicalExpression>;
1015 const leftPath = expr.get("left");
947 - invariant(
948 - leftPath.isExpression(),
949 - "Private names may not appear as the left hand side of a binary expression"
950 - );
1016 + if (!leftPath.isExpression()) {
1017 + builder.pushError({
1018 + reason:
1019 + "Private names may not appear as the left hand side of a logical expression",
1020 + severity: ErrorSeverity.InvalidInput,
1021 + nodePath: leftPath,
1022 + });
1023 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1024 + }
1025 const operator = expr.node.operator;
1026 switch (operator) {
1027 case "||": {
@@ -1050,11 +1124,14 @@ function lowerExpression(
1124 "^=": "^",
1125 };
1126 const binaryOperator = operators[operator];
1053 - invariant(
1054 - binaryOperator != null,
1055 - `Unhandled assignment operator '${operator}'`
1056 - );
1057 -
1127 + if (binaryOperator == null) {
1128 + builder.pushError({
1129 + reason: `Unhandled assignment operator '${operator}'`,
1130 + severity: ErrorSeverity.InvalidInput,
1131 + nodePath: expr.get("operator"),
1132 + });
1133 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1134 + }
1135 const left = expr.get("left");
1136 const leftNode = left.node;
1137 switch (leftNode.type) {
@@ -1086,10 +1163,19 @@ function lowerExpression(
1163 );
1164 // Extract the final property to be read from and re-assigned, eg 'c'
1165 const property = leftExpr.get("property");
1089 - invariant(
1090 - property.isIdentifier(),
1091 - "Assignment expression to dynamic properties is not yet supported"
1092 - );
1166 + if (!property.isIdentifier()) {
1167 + builder.pushError({
1168 + reason:
1169 + "Assignment expression to dynamic properties is not yet supported",
1170 + severity: ErrorSeverity.Todo,
1171 + nodePath: property,
1172 + });
1173 + return {
1174 + kind: "UnsupportedNode",
1175 + node: leftExpr.node,
1176 + loc: leftExpr.node.loc ?? GeneratedSource,
1177 + };
1178 + }
1179 // Store the previous value to a temporary
1180 const previousValuePlace: Place = buildTemporaryPlace(
1181 builder,
@@ -1137,10 +1223,13 @@ function lowerExpression(
1223 };
1224 }
1225 default: {
1140 - invariant(
1141 - false,
1142 - "Assignment update expressions require the lvalue to be an identifier or member expression"
1143 - );
1226 + builder.pushError({
1227 + reason:
1228 + "Assignment update expressions require the lvalue to be an identifier or member expression",
1229 + severity: ErrorSeverity.InvalidInput,
1230 + nodePath: expr,
1231 + });
1232 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1233 }
1234 }
1235 }
@@ -1164,36 +1253,65 @@ function lowerExpression(
1253 .get("children")
1254 .map((child) => lowerJsxElement(builder, child));
1255 const props: Map<string, Place> = new Map();
1167 - opening.get("attributes").forEach((attribute) => {
1168 - todoInvariant(attribute.isJSXAttribute(), "handle spread attributes");
1256 + let hasError = false;
1257 + for (const attribute of opening.get("attributes")) {
1258 + if (!attribute.isJSXAttribute()) {
1259 + builder.pushError({
1260 + reason: "Handle spread attributes",
1261 + severity: ErrorSeverity.Todo,
1262 + nodePath: attribute,
1263 + });
1264 + hasError = true;
1265 + continue;
1266 + }
1267 const name = attribute.get("name");
1170 - todoInvariant(
1171 - name.isJSXIdentifier(),
1172 - "handle non-identifier jsx attribute names"
1173 - );
1268 + if (!name.isJSXIdentifier()) {
1269 + builder.pushError({
1270 + reason: "Handle non-identifier jsx attribute names",
1271 + severity: ErrorSeverity.Todo,
1272 + nodePath: name,
1273 + });
1274 + hasError = true;
1275 + continue;
1276 + }
1277 const valueExpr = attribute.get("value");
1278 let value;
1279 if (valueExpr.isJSXElement() || valueExpr.isStringLiteral()) {
1280 value = lowerExpressionToPlace(builder, valueExpr);
1281 } else {
1179 - todoInvariant(
1180 - valueExpr.isJSXExpressionContainer(),
1181 - "handle other non expr containers"
1182 - );
1282 + if (!valueExpr.isJSXExpressionContainer()) {
1283 + builder.pushError({
1284 + reason: "Handle other non expr containers",
1285 + severity: ErrorSeverity.Todo,
1286 + nodePath: valueExpr,
1287 + });
1288 + hasError = true;
1289 + continue;
1290 + }
1291 const expression = valueExpr.get("expression");
1184 - todoInvariant(expression.isExpression(), "handle empty expressions");
1292 + if (!expression.isExpression()) {
1293 + builder.pushError({
1294 + reason: "Handle empty expressions",
1295 + severity: ErrorSeverity.Todo,
1296 + nodePath: valueExpr,
1297 + });
1298 + hasError = true;
1299 + continue;
1300 + }
1301 value = lowerExpressionToPlace(builder, expression);
1302 }
1303 const prop: string = name.node.name;
1304 props.set(prop, value);
1189 - });
1190 - return {
1191 - kind: "JsxExpression",
1192 - tag,
1193 - props,
1194 - children,
1195 - loc: exprLoc,
1196 - };
1305 + }
1306 + return hasError
1307 + ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
1308 + : {
1309 + kind: "JsxExpression",
1310 + tag,
1311 + props,
1312 + children,
1313 + loc: exprLoc,
1314 + };
1315 }
1316 case "JSXFragment": {
1317 const expr = exprPath as NodePath<t.JSXFragment>;
@@ -1228,27 +1346,40 @@ function lowerExpression(
1346 }
1347 loweredFunc = lowering.unwrap();
1348
1231 - const params: Array<string> = expr.get("params").map((p) => {
1232 - todoInvariant(p.isIdentifier(), "handle non identifier params");
1233 - return p.node.name;
1234 - });
1235 - return {
1236 - kind: "FunctionExpression",
1237 - name,
1238 - body,
1239 - params,
1240 - loweredFunc,
1241 - dependencies,
1242 - mutatedDeps: [],
1243 - loc: exprLoc,
1244 - };
1349 + let hasError = false;
1350 + const params: Array<string> = [];
1351 + for (const p of expr.get("params")) {
1352 + if (!p.isIdentifier()) {
1353 + builder.pushError({
1354 + reason: "Handle non identifier params",
1355 + severity: ErrorSeverity.Todo,
1356 + nodePath: p,
1357 + });
1358 + hasError = true;
1359 + continue;
1360 + }
1361 + params.push(p.node.name);
1362 + }
1363 + return hasError
1364 + ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
1365 + : {
1366 + kind: "FunctionExpression",
1367 + name,
1368 + body,
1369 + params,
1370 + loweredFunc,
1371 + dependencies,
1372 + mutatedDeps: [],
1373 + loc: exprLoc,
1374 + };
1375 }
1376 default: {
1247 - todo(`lowerExpression(${exprNode.type})`);
1248 - // assertExhaustive(
1249 - // exprNode,
1250 - // `Unexpected expression kind '${exprNode.type}'`
1251 - // );
1377 + builder.pushError({
1378 + reason: `Unhandled expression type: ${exprPath.type}`,
1379 + severity: ErrorSeverity.Todo,
1380 + nodePath: exprPath,
1381 + });
1382 + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1383 }
1384 }
1385 }
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md
+126 -33
@@ -11,6 +11,18 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
11 with (true) {
12 }
13
14 + const g = { ...a };
15 + const h = [...b];
16 + new c(...args);
17 + c(...args);
18 + g["e"] += 1;
19 +
20 + <Button {...args}></Button>;
21 + <Button xlink:href="localhost:3000"></Button>;
22 + <Button haha={1}></Button>;
23 +
24 + const j = function bar([quz, qux], ...args) {};
25 +
26 for (; i < 3; i += 1) {
27 x.push(i);
28 }
@@ -77,58 +89,139 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
89 | ^
90 8 | }
91 9 |
80 - 10 | for (; i < 3; i += 1) {
92 + 10 | const g = { ...a };
93
82 -[ReactForget] TodoError: Support non-variable initialization in for
94 +[ReactForget] TodoError: Handle object property spread
95 8 | }
96 9 |
85 -> 10 | for (; i < 3; i += 1) {
97 +> 10 | const g = { ...a };
98 + | ^^^^
99 + 11 | const h = [...b];
100 + 12 | new c(...args);
101 + 13 | c(...args);
102 +
103 +[ReactForget] TodoError: Handle non-expression array elements
104 + 9 |
105 + 10 | const g = { ...a };
106 +> 11 | const h = [...b];
107 + | ^^^^
108 + 12 | new c(...args);
109 + 13 | c(...args);
110 + 14 | g["e"] += 1;
111 +
112 +[ReactForget] TodoError: Support non-expression arguments to NewExpression
113 + 10 | const g = { ...a };
114 + 11 | const h = [...b];
115 +> 12 | new c(...args);
116 + | ^^^^^^^
117 + 13 | c(...args);
118 + 14 | g["e"] += 1;
119 + 15 |
120 +
121 +[ReactForget] TodoError: Support non-expression arguments to CallExpression
122 + 11 | const h = [...b];
123 + 12 | new c(...args);
124 +> 13 | c(...args);
125 + | ^^^^^^^
126 + 14 | g["e"] += 1;
127 + 15 |
128 + 16 | <Button {...args}></Button>;
129 +
130 +[ReactForget] TodoError: Assignment expression to dynamic properties is not yet supported
131 + 12 | new c(...args);
132 + 13 | c(...args);
133 +> 14 | g["e"] += 1;
134 + | ^^^
135 + 15 |
136 + 16 | <Button {...args}></Button>;
137 + 17 | <Button xlink:href="localhost:3000"></Button>;
138 +
139 +[ReactForget] TodoError: Handle spread attributes
140 + 14 | g["e"] += 1;
141 + 15 |
142 +> 16 | <Button {...args}></Button>;
143 + | ^^^^^^^^^
144 + 17 | <Button xlink:href="localhost:3000"></Button>;
145 + 18 | <Button haha={1}></Button>;
146 + 19 |
147 +
148 +[ReactForget] TodoError: Handle non-identifier jsx attribute names
149 + 15 |
150 + 16 | <Button {...args}></Button>;
151 +> 17 | <Button xlink:href="localhost:3000"></Button>;
152 + | ^^^^^^^^^^
153 + 18 | <Button haha={1}></Button>;
154 + 19 |
155 + 20 | const j = function bar([quz, qux], ...args) {};
156 +
157 +[ReactForget] TodoError: Handle non identifier params
158 + 18 | <Button haha={1}></Button>;
159 + 19 |
160 +> 20 | const j = function bar([quz, qux], ...args) {};
161 + | ^^^^^^^^^^
162 + 21 |
163 + 22 | for (; i < 3; i += 1) {
164 + 23 | x.push(i);
165 +
166 +[ReactForget] TodoError: Handle non identifier params
167 + 18 | <Button haha={1}></Button>;
168 + 19 |
169 +> 20 | const j = function bar([quz, qux], ...args) {};
170 + | ^^^^^^^
171 + 21 |
172 + 22 | for (; i < 3; i += 1) {
173 + 23 | x.push(i);
174 +
175 +[ReactForget] TodoError: Support non-variable initialization in for
176 + 20 | const j = function bar([quz, qux], ...args) {};
177 + 21 |
178 +> 22 | for (; i < 3; i += 1) {
179 | ^
87 - 11 | x.push(i);
88 - 12 | }
89 - 13 | for (; i < 3; ) {}
180 + 23 | x.push(i);
181 + 24 | }
182 + 25 | for (; i < 3; ) {}
183
184 [ReactForget] TodoError: Support non-variable initialization in for
92 - 11 | x.push(i);
93 - 12 | }
94 -> 13 | for (; i < 3; ) {}
185 + 23 | x.push(i);
186 + 24 | }
187 +> 25 | for (; i < 3; ) {}
188 | ^^^^^^^^^^^^^^^^^^
96 - 14 | for (;;) {}
97 - 15 | }
98 - 16 |
189 + 26 | for (;;) {}
190 + 27 | }
191 + 28 |
192
193 [ReactForget] TodoError: Handle empty for updater
101 - 11 | x.push(i);
102 - 12 | }
103 -> 13 | for (; i < 3; ) {}
194 + 23 | x.push(i);
195 + 24 | }
196 +> 25 | for (; i < 3; ) {}
197 | ^^^^^^^^^^^^^^^^^^
105 - 14 | for (;;) {}
106 - 15 | }
107 - 16 |
198 + 26 | for (;;) {}
199 + 27 | }
200 + 28 |
201
202 [ReactForget] TodoError: Support non-variable initialization in for
110 - 12 | }
111 - 13 | for (; i < 3; ) {}
112 -> 14 | for (;;) {}
203 + 24 | }
204 + 25 | for (; i < 3; ) {}
205 +> 26 | for (;;) {}
206 | ^^^^^^^^^^^
114 - 15 | }
115 - 16 |
207 + 27 | }
208 + 28 |
209
210 [ReactForget] TodoError: Handle empty for updater
118 - 12 | }
119 - 13 | for (; i < 3; ) {}
120 -> 14 | for (;;) {}
211 + 24 | }
212 + 25 | for (; i < 3; ) {}
213 +> 26 | for (;;) {}
214 | ^^^^^^^^^^^
122 - 15 | }
123 - 16 |
215 + 27 | }
216 + 28 |
217
218 [ReactForget] TodoError: ForStatement without test
126 - 12 | }
127 - 13 | for (; i < 3; ) {}
128 -> 14 | for (;;) {}
219 + 24 | }
220 + 25 | for (; i < 3; ) {}
221 +> 26 | for (;;) {}
222 | ^^^^^^^^^^^
130 - 15 | }
131 - 16 |
223 + 27 | }
224 + 28 |
225 ```
226
227
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js
+12
@@ -7,6 +7,18 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
7 with (true) {
8 }
9
10 + const g = { ...a };
11 + const h = [...b];
12 + new c(...args);
13 + c(...args);
14 + g["e"] += 1;
15 +
16 + <Button {...args}></Button>;
17 + <Button xlink:href="localhost:3000"></Button>;
18 + <Button haha={1}></Button>;
19 +
20 + const j = function bar([quz, qux], ...args) {};
21 +
22 for (; i < 3; i += 1) {
23 x.push(i);
24 }