@samitouri / QOS-React / commits / 47074a2def

Support JsxSpreadAttribute

Changes the representation of JsxElement props to be an array of attributes, each of which can be a named attribute or spread attribute.

Joe Savona committed Jan 31, 2023 at 16:43 UTC 47074a2def971e7afd807ea728f6ec4564d7ab5c
8 files changed +138 -25
compiler/forget/src/HIR/BuildHIR.ts
+11 -2
@@ -22,6 +22,7 @@ import {
22 IfTerminal,
23 InstructionKind,
24 InstructionValue,
25 + JsxAttribute,
26 makeInstructionId,
27 Place,
28 ReturnTerminal,
@@ -1207,9 +1208,17 @@ function lowerExpression(
1208 const children = expr
1209 .get("children")
1210 .map((child) => lowerJsxElement(builder, child));
1210 - const props: Map<string, Place> = new Map();
1211 + const props: Array<JsxAttribute> = [];
1212 let hasError = false;
1213 for (const attribute of opening.get("attributes")) {
1214 + if (attribute.isJSXSpreadAttribute()) {
1215 + const argument = lowerExpressionToPlace(
1216 + builder,
1217 + attribute.get("argument")
1218 + );
1219 + props.push({ kind: "JsxSpreadAttribute", argument });
1220 + continue;
1221 + }
1222 if (!attribute.isJSXAttribute()) {
1223 builder.errors.push({
1224 reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
@@ -1256,7 +1265,7 @@ function lowerExpression(
1265 value = lowerExpressionToPlace(builder, expression);
1266 }
1267 const prop: string = name.node.name;
1259 - props.set(prop, value);
1268 + props.push({ kind: "JsxAttribute", name: prop, place: value });
1269 }
1270 return hasError
1271 ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
compiler/forget/src/HIR/HIR.ts
+5 -1
@@ -419,7 +419,7 @@ export type InstructionData =
419 | {
420 kind: "JsxExpression";
421 tag: Place;
422 - props: Map<string, Place>;
422 + props: Array<JsxAttribute>;
423 children: Array<Place> | null; // null === no children
424 }
425 | {
@@ -465,6 +465,10 @@ export type InstructionData =
465 node: t.Node;
466 };
467
468 +export type JsxAttribute =
469 + | { kind: "JsxSpreadAttribute"; argument: Place }
470 + | { kind: "JsxAttribute"; name: string; place: Place };
471 +
472 /**
473 * A place where data may be read from / written to:
474 * - a variable (identifier)
compiler/forget/src/HIR/PrintHIR.ts
+6 -2
@@ -261,8 +261,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
261 }
262 case "JsxExpression": {
263 const propItems = [];
264 - for (const [prop, value] of instrValue.props) {
265 - propItems.push(`${prop}={${printPlace(value)}}`);
264 + for (const attribute of instrValue.props) {
265 + if (attribute.kind === "JsxAttribute") {
266 + propItems.push(`${attribute.name}={${printPlace(attribute.place)}}`);
267 + } else {
268 + propItems.push(`...${printPlace(attribute.argument)}`);
269 + }
270 }
271 const props = propItems.length !== 0 ? " " + propItems.join(" ") : "";
272 if (instrValue.children !== null) {
compiler/forget/src/HIR/visitors.ts
+35 -3
@@ -78,7 +78,24 @@ export function* eachInstructionValueOperand(
78 }
79 case "JsxExpression": {
80 yield instrValue.tag;
81 - yield* instrValue.props.values();
81 + for (const attribute of instrValue.props) {
82 + switch (attribute.kind) {
83 + case "JsxAttribute": {
84 + yield attribute.place;
85 + break;
86 + }
87 + case "JsxSpreadAttribute": {
88 + yield attribute.argument;
89 + break;
90 + }
91 + default: {
92 + assertExhaustive(
93 + attribute,
94 + `Unexpected attribute kind '${(attribute as any).kind}'`
95 + );
96 + }
97 + }
98 + }
99 if (instrValue.children) {
100 yield* instrValue.children;
101 }
@@ -178,8 +195,23 @@ export function mapInstructionOperands(
195 }
196 case "JsxExpression": {
197 instrValue.tag = fn(instrValue.tag);
181 - for (const [prop, place] of instrValue.props) {
182 - instrValue.props.set(prop, fn(place));
198 + for (const attribute of instrValue.props) {
199 + switch (attribute.kind) {
200 + case "JsxAttribute": {
201 + attribute.place = fn(attribute.place);
202 + break;
203 + }
204 + case "JsxSpreadAttribute": {
205 + attribute.argument = fn(attribute.argument);
206 + break;
207 + }
208 + default: {
209 + assertExhaustive(
210 + attribute,
211 + `Unexpected attribute kind '${(attribute as any).kind}'`
212 + );
213 + }
214 + }
215 }
216 if (instrValue.children) {
217 instrValue.children = instrValue.children.map((p) => fn(p));
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+25 -8
@@ -535,14 +535,31 @@ function codegenInstructionValue(
535 break;
536 }
537 case "JsxExpression": {
538 - const attributes: Array<t.JSXAttribute> = [];
539 - for (const [prop, value] of instrValue.props) {
540 - attributes.push(
541 - t.jsxAttribute(
542 - t.jsxIdentifier(prop),
543 - t.jsxExpressionContainer(codegenPlace(cx, value))
544 - )
545 - );
538 + const attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute> = [];
539 + for (const attribute of instrValue.props) {
540 + switch (attribute.kind) {
541 + case "JsxAttribute": {
542 + attributes.push(
543 + t.jsxAttribute(
544 + t.jsxIdentifier(attribute.name),
545 + t.jsxExpressionContainer(codegenPlace(cx, attribute.place))
546 + )
547 + );
548 + break;
549 + }
550 + case "JsxSpreadAttribute": {
551 + attributes.push(
552 + t.jsxSpreadAttribute(codegenPlace(cx, attribute.argument))
553 + );
554 + break;
555 + }
556 + default: {
557 + assertExhaustive(
558 + attribute,
559 + `Unexpected attribute kind '${(attribute as any).kind}'`
560 + );
561 + }
562 + }
563 }
564 let tagValue = codegenPlace(cx, instrValue.tag);
565 let tag: string;
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md
-9
@@ -187,15 +187,6 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
187 20 | <Button {...args}></Button>;
188 21 | <Button xlink:href="localhost:3000"></Button>;
189
190 -[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXSpreadAttribute attributes in JSXElement
191 - 18 | const { z, aa = "aa", ...zz } = useCustom();
192 - 19 |
193 -> 20 | <Button {...args}></Button>;
194 - | ^^^^^^^^^
195 - 21 | <Button xlink:href="localhost:3000"></Button>;
196 - 22 | <Button haha={1}></Button>;
197 - 23 | <Button>{/** empty */}</Button>;
198 -
190 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement
191 19 |
192 20 | <Button {...args}></Button>;
compiler/forget/src/__tests__/fixtures/hir/jsx-spread.expect.md new
+51
@@ -0,0 +1,51 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + return (
7 + <Component {...props} {...{ bar: props.cond ? props.foo : props.bar }} />
8 + );
9 +}
10 +
11 +```
12 +
13 +## Code
14 +
15 +```javascript
16 +function Component(props) {
17 + const $ = React.useMemoCache();
18 + const c_0 = $[0] !== props;
19 + let t1;
20 + if (c_0) {
21 + t1 = props.cond ? props.foo : props.bar;
22 + $[0] = props;
23 + $[1] = t1;
24 + } else {
25 + t1 = $[1];
26 + }
27 + const c_2 = $[2] !== t1;
28 + let t3;
29 + if (c_2) {
30 + t3 = { bar: t1 };
31 + $[2] = t1;
32 + $[3] = t3;
33 + } else {
34 + t3 = $[3];
35 + }
36 + const c_4 = $[4] !== props;
37 + const c_5 = $[5] !== t3;
38 + let t6;
39 + if (c_4 || c_5) {
40 + t6 = <Component {...props} {...t3}></Component>;
41 + $[4] = props;
42 + $[5] = t3;
43 + $[6] = t6;
44 + } else {
45 + t6 = $[6];
46 + }
47 + return t6;
48 +}
49 +
50 +```
51 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/jsx-spread.js new
+5
@@ -0,0 +1,5 @@
1 +function Component(props) {
2 + return (
3 + <Component {...props} {...{ bar: props.cond ? props.foo : props.bar }} />
4 + );
5 +}