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
+}