diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 55a6839fef..4a7e167fa8 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -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 = new Map(); + const props: Array = []; 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 } diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index c04e4faf06..cb15ba7126 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -419,7 +419,7 @@ export type InstructionData = | { kind: "JsxExpression"; tag: Place; - props: Map; + props: Array; children: Array | 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) diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index a6e1acfae6..32179e8ec6 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -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) { diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 55e306433e..3c6763765d 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -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)); diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 943a7b6375..2c6565e6da 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -535,14 +535,31 @@ function codegenInstructionValue( break; } case "JsxExpression": { - const attributes: Array = []; - for (const [prop, value] of instrValue.props) { - attributes.push( - t.jsxAttribute( - t.jsxIdentifier(prop), - t.jsxExpressionContainer(codegenPlace(cx, value)) - ) - ); + const attributes: Array = []; + 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; diff --git a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md index 8d64ed91a8..59bfe5cc2e 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md @@ -187,15 +187,6 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) { 20 | ; 21 | ; -[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXSpreadAttribute attributes in JSXElement - 18 | const { z, aa = "aa", ...zz } = useCustom(); - 19 | -> 20 | ; - | ^^^^^^^^^ - 21 | ; - 22 | ; - 23 | ; - [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle JSXNamespacedName attribute names in JSXElement 19 | 20 | ; diff --git a/compiler/forget/src/__tests__/fixtures/hir/jsx-spread.expect.md b/compiler/forget/src/__tests__/fixtures/hir/jsx-spread.expect.md new file mode 100644 index 0000000000..260fa1f7c6 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/jsx-spread.expect.md @@ -0,0 +1,51 @@ + +## Input + +```javascript +function Component(props) { + return ( + + ); +} + +``` + +## 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 = ; + $[4] = props; + $[5] = t3; + $[6] = t6; + } else { + t6 = $[6]; + } + return t6; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/jsx-spread.js b/compiler/forget/src/__tests__/fixtures/hir/jsx-spread.js new file mode 100644 index 0000000000..0dd5d5c96c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/jsx-spread.js @@ -0,0 +1,5 @@ +function Component(props) { + return ( + + ); +}