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

[hir] Add support for string literal property keys

Mostly reuses existing analysis of an identifier property key. Adds a new type field to ObjectProperty to propogate the type of the key. This is used in codegen to correctly emit a string literal or an identifier.

Sathya Gunasekaran committed Sep 7, 2023 at 13:56 UTC f2027b9a4dd3e906e9853cd1d32ee1e008290f19
16 files changed +353 -64
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+37 -24
@@ -967,6 +967,33 @@ function lowerStatement(
967 }
968 }
969
970 +function lowerObjectPropertyKey(
971 + builder: HIRBuilder,
972 + key: t.PrivateName | t.Expression
973 +): { name: string; type: "string" | "identifier" } | null {
974 + if (key.type === "Identifier") {
975 + return {
976 + name: key.name,
977 + type: "identifier",
978 + };
979 + }
980 +
981 + if (key.type === "StringLiteral") {
982 + return {
983 + name: key.value,
984 + type: "string",
985 + };
986 + }
987 +
988 + builder.errors.push({
989 + reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
990 + severity: ErrorSeverity.Todo,
991 + loc: key.loc ?? null,
992 + suggestions: null,
993 + });
994 + return null;
995 +}
996 +
997 function lowerExpression(
998 builder: HIRBuilder,
999 exprPath: NodePath<t.Expression>
@@ -1009,19 +1036,11 @@ function lowerExpression(
1036 const properties: Array<ObjectProperty | SpreadPattern> = [];
1037 for (const propertyPath of propertyPaths) {
1038 if (propertyPath.isObjectProperty()) {
1012 - const key = propertyPath.node.key;
1013 - let keyName: string;
1014 - if (key.type === "Identifier") {
1015 - keyName = key.name;
1016 - } else if (key.type === "StringLiteral") {
1017 - keyName = key.value;
1018 - } else {
1019 - builder.errors.push({
1020 - reason: `(BuildHIR::lowerExpression) Expected Identifier, got ${key.type} key in ObjectExpression`,
1021 - severity: ErrorSeverity.Todo,
1022 - loc: propertyPath.node.loc ?? null,
1023 - suggestions: null,
1024 - });
1039 + const loweredKey = lowerObjectPropertyKey(
1040 + builder,
1041 + propertyPath.node.key
1042 + );
1043 + if (!loweredKey) {
1044 continue;
1045 }
1046 const valuePath = propertyPath.get("value");
@@ -1037,8 +1056,8 @@ function lowerExpression(
1056 const value = lowerExpressionToTemporary(builder, valuePath);
1057 properties.push({
1058 kind: "ObjectProperty",
1040 - name: keyName,
1059 place: value,
1060 + ...loweredKey,
1061 });
1062 } else if (propertyPath.isSpreadElement()) {
1063 const place = lowerExpressionToTemporary(
@@ -2800,14 +2819,8 @@ function lowerAssignment(
2819 });
2820 continue;
2821 }
2803 - const key = property.get("key");
2804 - if (!key.isIdentifier()) {
2805 - builder.errors.push({
2806 - reason: `(BuildHIR::lowerAssignment) Handle ${key.type} keys in ObjectPattern`,
2807 - severity: ErrorSeverity.Todo,
2808 - loc: key.node.loc ?? null,
2809 - suggestions: null,
2810 - });
2822 + const loweredKey = lowerObjectPropertyKey(builder, property.node.key);
2823 + if (!loweredKey) {
2824 continue;
2825 }
2826 const element = property.get("value");
@@ -2832,8 +2845,8 @@ function lowerAssignment(
2845 }
2846 properties.push({
2847 kind: "ObjectProperty",
2835 - name: key.node.name,
2848 place: identifier,
2849 + ...loweredKey,
2850 });
2851 } else {
2852 const temp = buildTemporaryPlace(
@@ -2842,8 +2855,8 @@ function lowerAssignment(
2855 );
2856 properties.push({
2857 kind: "ObjectProperty",
2845 - name: key.node.name,
2858 place: { ...temp },
2859 + ...loweredKey,
2860 });
2861 followups.push({ place: temp, path: element as NodePath<t.LVal> }); // TODO remove type cast
2862 }
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+1
@@ -501,6 +501,7 @@ export type ObjectPattern = {
501
502 export type ObjectProperty = {
503 kind: "ObjectProperty";
504 + type: "string" | "identifier";
505 name: string; // TODO: make a Place
506 place: Place;
507 };
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+16 -4
@@ -16,6 +16,7 @@ import {
16 IdentifierId,
17 InstructionKind,
18 JsxAttribute,
19 + ObjectProperty,
20 Pattern,
21 Place,
22 ReactiveBlock,
@@ -898,14 +899,14 @@ function codegenInstructionValue(
899 const properties = [];
900 for (const property of instrValue.properties) {
901 if (property.kind === "ObjectProperty") {
901 - const key = t.identifier(property.name);
902 + const key = codegenObjectPropertyKey(property);
903 const value = codegenPlace(cx, property.place);
904 properties.push(
905 t.objectProperty(
906 key,
907 value,
908 false,
908 - value.type === "Identifier" && value.name === key.name
909 + value.type === "Identifier" && value.name === property.name
910 )
911 );
912 } else {
@@ -1310,6 +1311,17 @@ function convertMemberExpressionToJsx(
1311 }
1312 }
1313
1314 +function codegenObjectPropertyKey(
1315 + property: ObjectProperty
1316 +): t.StringLiteral | t.Identifier {
1317 + switch (property.type) {
1318 + case "identifier":
1319 + return t.identifier(property.name);
1320 + case "string":
1321 + return t.stringLiteral(property.name);
1322 + }
1323 +}
1324 +
1325 function codegenLValue(
1326 pattern: Pattern | Place | SpreadPattern
1327 ): t.ArrayPattern | t.ObjectPattern | t.RestElement | t.Identifier {
@@ -1328,13 +1340,13 @@ function codegenLValue(
1340 return t.objectPattern(
1341 pattern.properties.map((property) => {
1342 if (property.kind === "ObjectProperty") {
1331 - const key = t.identifier(property.name);
1343 + const key = codegenObjectPropertyKey(property);
1344 const value = codegenLValue(property.place);
1345 return t.objectProperty(
1346 key,
1347 value,
1348 false,
1337 - value.type === "Identifier" && value.name === key.name
1349 + value.type === "Identifier" && value.name === property.name
1350 );
1351 } else {
1352 return t.restElement(codegenLValue(property.place));
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-mixed-property-key-types.expect.md new
+53
@@ -0,0 +1,53 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo() {
6 + const {
7 + "data-foo-bar": x,
8 + a: y,
9 + data: z,
10 + } = { "data-foo-bar": 1, a: 2, data: 3 };
11 + return [x, y, z];
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: foo,
16 + params: [],
17 + isComponent: false,
18 +};
19 +
20 +```
21 +
22 +## Code
23 +
24 +```javascript
25 +import { unstable_useMemoCache as useMemoCache } from "react";
26 +function foo() {
27 + const $ = useMemoCache(2);
28 + let t0;
29 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30 + t0 = { "data-foo-bar": 1, a: 2, data: 3 };
31 + $[0] = t0;
32 + } else {
33 + t0 = $[0];
34 + }
35 + const { "data-foo-bar": x, a: y, data: z } = t0;
36 + let t1;
37 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
38 + t1 = [x, y, z];
39 + $[1] = t1;
40 + } else {
41 + t1 = $[1];
42 + }
43 + return t1;
44 +}
45 +
46 +export const FIXTURE_ENTRYPOINT = {
47 + fn: foo,
48 + params: [],
49 + isComponent: false,
50 +};
51 +
52 +```
53 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-mixed-property-key-types.js new
+14
@@ -0,0 +1,14 @@
1 +function foo() {
2 + const {
3 + "data-foo-bar": x,
4 + a: y,
5 + data: z,
6 + } = { "data-foo-bar": 1, a: 2, data: 3 };
7 + return [x, y, z];
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: foo,
12 + params: [],
13 + isComponent: false,
14 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key-invalid-identifier.expect.md new
+32
@@ -0,0 +1,32 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo({ "data-foo-bar": dataTestID }) {
6 + return dataTestID;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: foo,
11 + params: [{ "data-foo-bar": {} }],
12 + isComponent: false,
13 +};
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +function foo(t5) {
21 + const { "data-foo-bar": dataTestID } = t5;
22 + return dataTestID;
23 +}
24 +
25 +export const FIXTURE_ENTRYPOINT = {
26 + fn: foo,
27 + params: [{ "data-foo-bar": {} }],
28 + isComponent: false,
29 +};
30 +
31 +```
32 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key-invalid-identifier.js new
+9
@@ -0,0 +1,9 @@
1 +function foo({ "data-foo-bar": dataTestID }) {
2 + return dataTestID;
3 +}
4 +
5 +export const FIXTURE_ENTRYPOINT = {
6 + fn: foo,
7 + params: [{ "data-foo-bar": {} }],
8 + isComponent: false,
9 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key.expect.md new
+32
@@ -0,0 +1,32 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo({ data: dataTestID }) {
6 + return dataTestID;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: foo,
11 + params: [{ data: {} }],
12 + isComponent: false,
13 +};
14 +
15 +```
16 +
17 +## Code
18 +
19 +```javascript
20 +function foo(t5) {
21 + const { data: dataTestID } = t5;
22 + return dataTestID;
23 +}
24 +
25 +export const FIXTURE_ENTRYPOINT = {
26 + fn: foo,
27 + params: [{ data: {} }],
28 + isComponent: false,
29 +};
30 +
31 +```
32 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-param-string-literal-key.js new
+9
@@ -0,0 +1,9 @@
1 +function foo({ data: dataTestID }) {
2 + return dataTestID;
3 +}
4 +
5 +export const FIXTURE_ENTRYPOINT = {
6 + fn: foo,
7 + params: [{ data: {} }],
8 + isComponent: false,
9 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-invalid-identifier-property-key.expect.md new
+42
@@ -0,0 +1,42 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo() {
6 + const { "data-foo-bar": t } = { "data-foo-bar": 1 };
7 + return t;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: foo,
12 + params: [],
13 + isComponent: false,
14 +};
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +import { unstable_useMemoCache as useMemoCache } from "react";
22 +function foo() {
23 + const $ = useMemoCache(1);
24 + let t0;
25 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
26 + t0 = { "data-foo-bar": 1 };
27 + $[0] = t0;
28 + } else {
29 + t0 = $[0];
30 + }
31 + const { "data-foo-bar": t } = t0;
32 + return t;
33 +}
34 +
35 +export const FIXTURE_ENTRYPOINT = {
36 + fn: foo,
37 + params: [],
38 + isComponent: false,
39 +};
40 +
41 +```
42 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-invalid-identifier-property-key.js new
+10
@@ -0,0 +1,10 @@
1 +function foo() {
2 + const { "data-foo-bar": t } = { "data-foo-bar": 1 };
3 + return t;
4 +}
5 +
6 +export const FIXTURE_ENTRYPOINT = {
7 + fn: foo,
8 + params: [],
9 + isComponent: false,
10 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-property-key.expect.md new
+42
@@ -0,0 +1,42 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo() {
6 + const { data: t } = { data: 1 };
7 + return t;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: foo,
12 + params: [],
13 + isComponent: false,
14 +};
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +import { unstable_useMemoCache as useMemoCache } from "react";
22 +function foo() {
23 + const $ = useMemoCache(1);
24 + let t0;
25 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
26 + t0 = { data: 1 };
27 + $[0] = t0;
28 + } else {
29 + t0 = $[0];
30 + }
31 + const { data: t } = t0;
32 + return t;
33 +}
34 +
35 +export const FIXTURE_ENTRYPOINT = {
36 + fn: foo,
37 + params: [],
38 + isComponent: false,
39 +};
40 +
41 +```
42 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-string-literal-property-key.js new
+10
@@ -0,0 +1,10 @@
1 +function foo() {
2 + const { data: t } = { data: 1 };
3 + return t;
4 +}
5 +
6 +export const FIXTURE_ENTRYPOINT = {
7 + fn: foo,
8 + params: [],
9 + isComponent: false,
10 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-validate-identifiers-object-key.expect.md deleted
-36
@@ -1,36 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function Foo() {
6 - return {
7 - "a.b": 1,
8 - "a\b": 2,
9 - "a/b": 3,
10 - "a+b": 4,
11 - "a b": 5,
12 - };
13 -}
14 -
15 -export const FIXTURE_ENTRYPOINT = {
16 - fn: Foo,
17 - params: [],
18 - isComponent: false,
19 -};
20 -
21 -```
22 -
23 -
24 -## Error
25 -
26 -```
27 -Unexpected token, expected "," (2:13)
28 - 1 | import { unstable_useMemoCache as useMemoCache } from "react";function Foo() {const $ = useMemoCache(1);let t0;if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29 -> 2 | t0 = { a.b: 1, a: 2, a/b: 3, a+b: 4, a b: 5 };$[0] = t0;} else {t0 = $[0];}return t0;}
30 - | ^
31 - 3 |
32 - 4 |
33 - 5 |
34 -```
35 -
36 -
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-validate-identifiers-object-key.expect.md new
+46
@@ -0,0 +1,46 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Foo() {
6 + return {
7 + "a.b": 1,
8 + "a\b": 2,
9 + "a/b": 3,
10 + "a+b": 4,
11 + "a b": 5,
12 + };
13 +}
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Foo,
17 + params: [],
18 + isComponent: false,
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { unstable_useMemoCache as useMemoCache } from "react";
27 +function Foo() {
28 + const $ = useMemoCache(1);
29 + let t0;
30 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31 + t0 = { "a.b": 1, "a\b": 2, "a/b": 3, "a+b": 4, "a b": 5 };
32 + $[0] = t0;
33 + } else {
34 + t0 = $[0];
35 + }
36 + return t0;
37 +}
38 +
39 +export const FIXTURE_ENTRYPOINT = {
40 + fn: Foo,
41 + params: [],
42 + isComponent: false,
43 +};
44 +
45 +```
46 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/todo-validate-identifiers-object-key.ts renamed