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.
This commit is contained in:
Joe Savona
2023-01-31 16:43:46 -08:00
parent df07522b89
commit 47074a2def
8 changed files with 138 additions and 25 deletions
+11 -2
View File
@@ -22,6 +22,7 @@ import {
IfTerminal,
InstructionKind,
InstructionValue,
JsxAttribute,
makeInstructionId,
Place,
ReturnTerminal,
@@ -1207,9 +1208,17 @@ function lowerExpression(
const children = expr
.get("children")
.map((child) => lowerJsxElement(builder, child));
const props: Map<string, Place> = new Map();
const props: Array<JsxAttribute> = [];
let hasError = false;
for (const attribute of opening.get("attributes")) {
if (attribute.isJSXSpreadAttribute()) {
const argument = lowerExpressionToPlace(
builder,
attribute.get("argument")
);
props.push({ kind: "JsxSpreadAttribute", argument });
continue;
}
if (!attribute.isJSXAttribute()) {
builder.errors.push({
reason: `(BuildHIR::lowerExpression) Handle ${attribute.type} attributes in JSXElement`,
@@ -1256,7 +1265,7 @@ function lowerExpression(
value = lowerExpressionToPlace(builder, expression);
}
const prop: string = name.node.name;
props.set(prop, value);
props.push({ kind: "JsxAttribute", name: prop, place: value });
}
return hasError
? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
+5 -1
View File
@@ -419,7 +419,7 @@ export type InstructionData =
| {
kind: "JsxExpression";
tag: Place;
props: Map<string, Place>;
props: Array<JsxAttribute>;
children: Array<Place> | null; // null === no children
}
| {
@@ -465,6 +465,10 @@ export type InstructionData =
node: t.Node;
};
export type JsxAttribute =
| { kind: "JsxSpreadAttribute"; argument: Place }
| { kind: "JsxAttribute"; name: string; place: Place };
/**
* A place where data may be read from / written to:
* - a variable (identifier)
+6 -2
View File
@@ -261,8 +261,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
}
case "JsxExpression": {
const propItems = [];
for (const [prop, value] of instrValue.props) {
propItems.push(`${prop}={${printPlace(value)}}`);
for (const attribute of instrValue.props) {
if (attribute.kind === "JsxAttribute") {
propItems.push(`${attribute.name}={${printPlace(attribute.place)}}`);
} else {
propItems.push(`...${printPlace(attribute.argument)}`);
}
}
const props = propItems.length !== 0 ? " " + propItems.join(" ") : "";
if (instrValue.children !== null) {
+35 -3
View File
@@ -78,7 +78,24 @@ export function* eachInstructionValueOperand(
}
case "JsxExpression": {
yield instrValue.tag;
yield* instrValue.props.values();
for (const attribute of instrValue.props) {
switch (attribute.kind) {
case "JsxAttribute": {
yield attribute.place;
break;
}
case "JsxSpreadAttribute": {
yield attribute.argument;
break;
}
default: {
assertExhaustive(
attribute,
`Unexpected attribute kind '${(attribute as any).kind}'`
);
}
}
}
if (instrValue.children) {
yield* instrValue.children;
}
@@ -178,8 +195,23 @@ export function mapInstructionOperands(
}
case "JsxExpression": {
instrValue.tag = fn(instrValue.tag);
for (const [prop, place] of instrValue.props) {
instrValue.props.set(prop, fn(place));
for (const attribute of instrValue.props) {
switch (attribute.kind) {
case "JsxAttribute": {
attribute.place = fn(attribute.place);
break;
}
case "JsxSpreadAttribute": {
attribute.argument = fn(attribute.argument);
break;
}
default: {
assertExhaustive(
attribute,
`Unexpected attribute kind '${(attribute as any).kind}'`
);
}
}
}
if (instrValue.children) {
instrValue.children = instrValue.children.map((p) => fn(p));
@@ -535,14 +535,31 @@ function codegenInstructionValue(
break;
}
case "JsxExpression": {
const attributes: Array<t.JSXAttribute> = [];
for (const [prop, value] of instrValue.props) {
attributes.push(
t.jsxAttribute(
t.jsxIdentifier(prop),
t.jsxExpressionContainer(codegenPlace(cx, value))
)
);
const attributes: Array<t.JSXAttribute | t.JSXSpreadAttribute> = [];
for (const attribute of instrValue.props) {
switch (attribute.kind) {
case "JsxAttribute": {
attributes.push(
t.jsxAttribute(
t.jsxIdentifier(attribute.name),
t.jsxExpressionContainer(codegenPlace(cx, attribute.place))
)
);
break;
}
case "JsxSpreadAttribute": {
attributes.push(
t.jsxSpreadAttribute(codegenPlace(cx, attribute.argument))
);
break;
}
default: {
assertExhaustive(
attribute,
`Unexpected attribute kind '${(attribute as any).kind}'`
);
}
}
}
let tagValue = codegenPlace(cx, instrValue.tag);
let tag: string;
@@ -187,15 +187,6 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
20 | <Button {...args}></Button>;
21 | <Button xlink:href="localhost:3000"></Button>;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXSpreadAttribute attributes in JSXElement
18 | const { z, aa = "aa", ...zz } = useCustom();
19 |
> 20 | <Button {...args}></Button>;
| ^^^^^^^^^
21 | <Button xlink:href="localhost:3000"></Button>;
22 | <Button haha={1}></Button>;
23 | <Button>{/** empty */}</Button>;
[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement
19 |
20 | <Button {...args}></Button>;
@@ -0,0 +1,51 @@
## Input
```javascript
function Component(props) {
return (
<Component {...props} {...{ bar: props.cond ? props.foo : props.bar }} />
);
}
```
## Code
```javascript
function Component(props) {
const $ = React.useMemoCache();
const c_0 = $[0] !== props;
let t1;
if (c_0) {
t1 = props.cond ? props.foo : props.bar;
$[0] = props;
$[1] = t1;
} else {
t1 = $[1];
}
const c_2 = $[2] !== t1;
let t3;
if (c_2) {
t3 = { bar: t1 };
$[2] = t1;
$[3] = t3;
} else {
t3 = $[3];
}
const c_4 = $[4] !== props;
const c_5 = $[5] !== t3;
let t6;
if (c_4 || c_5) {
t6 = <Component {...props} {...t3}></Component>;
$[4] = props;
$[5] = t3;
$[6] = t6;
} else {
t6 = $[6];
}
return t6;
}
```
@@ -0,0 +1,5 @@
function Component(props) {
return (
<Component {...props} {...{ bar: props.cond ? props.foo : props.bar }} />
);
}