diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index f12d52c174..d67b6846bf 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -1760,7 +1760,27 @@ function lowerJsxElementName( ): Place { const exprNode = exprPath.node; const exprLoc = exprNode.loc ?? GeneratedSource; - if (!exprPath.isJSXIdentifier()) { + if (exprPath.isJSXIdentifier()) { + const tag: string = exprPath.node.name; + if (tag.match(/^[A-Z]/)) { + return lowerIdentifier(builder, exprPath); + } else { + const place: Place = buildTemporaryPlace(builder, exprLoc); + builder.push({ + id: makeInstructionId(0), + value: { + kind: "Primitive", + value: tag, + loc: exprLoc, + }, + loc: exprLoc, + lvalue: { ...place }, + }); + return place; + } + } else if (exprPath.isJSXMemberExpression()) { + return lowerJsxMemberExpression(builder, exprPath); + } else { builder.errors.push({ reason: `(BuildHIR::lowerJsxElementName) Handle ${exprPath.type} tags`, severity: ErrorSeverity.Todo, @@ -1779,23 +1799,40 @@ function lowerJsxElementName( }); return { ...place }; } - const tag: string = exprPath.node.name; - if (tag.match(/^[A-Z]/)) { - return lowerIdentifier(builder, exprPath); +} + +function lowerJsxMemberExpression( + builder: HIRBuilder, + exprPath: NodePath +): Place { + const loc = exprPath.node.loc ?? GeneratedSource; + const object = exprPath.get("object"); + let objectPlace: Place; + if (object.isJSXMemberExpression()) { + objectPlace = lowerJsxMemberExpression(builder, object); } else { - const place: Place = buildTemporaryPlace(builder, exprLoc); - builder.push({ - id: makeInstructionId(0), - value: { - kind: "Primitive", - value: tag, - loc: exprLoc, - }, - loc: exprLoc, - lvalue: { ...place }, - }); - return place; + invariant( + object.isJSXIdentifier(), + "TypeScript refinement fail: expected 'JsxIdentifier', got '%s'", + object.node.type + ); + objectPlace = lowerIdentifier(builder, object); } + const place = buildTemporaryPlace(builder, loc); + const property = exprPath.get("property").node.name; + builder.push({ + id: makeInstructionId(0), + lvalue: { ...place }, + value: { + kind: "PropertyLoad", + object: objectPlace, + property, + optional: false, + loc, + }, + loc, + }); + return place; } function lowerJsxElement( diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 0ab1e24c93..b4883a352b 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -651,29 +651,26 @@ function codegenInstructionValue( } } let tagValue = codegenPlace(cx, instrValue.tag); - let tag: string; + let tag: t.JSXIdentifier | t.JSXMemberExpression; if (tagValue.type === "Identifier") { - tag = tagValue.name; + tag = t.jsxIdentifier(tagValue.name); + } else if (tagValue.type === "MemberExpression") { + tag = convertMemberExpressionToJsx(tagValue); } else { invariant( tagValue.type === "StringLiteral", - "Expected JSX tag to be an identifier or string" + "Expected JSX tag to be an identifier or string, got '%s'", + tagValue.type ); - tag = tagValue.value; + tag = t.jsxIdentifier(tagValue.value); } const children = instrValue.children !== null ? instrValue.children.map((child) => codegenJsxElement(cx, child)) : []; value = t.jsxElement( - t.jsxOpeningElement( - t.jsxIdentifier(tag), - attributes, - instrValue.children === null - ), - instrValue.children !== null - ? t.jsxClosingElement(t.jsxIdentifier(tag)) - : null, + t.jsxOpeningElement(tag, attributes, instrValue.children === null), + instrValue.children !== null ? t.jsxClosingElement(tag) : null, children, instrValue.children === null ); @@ -896,6 +893,26 @@ function codegenJsxElement( } } +function convertMemberExpressionToJsx( + expr: t.MemberExpression +): t.JSXMemberExpression { + invariant( + expr.property.type === "Identifier", + "Expected JSX member expression property to be a string" + ); + const property = t.jsxIdentifier(expr.property.name); + if (expr.object.type === "Identifier") { + return t.jsxMemberExpression(t.jsxIdentifier(expr.object.name), property); + } else { + invariant( + expr.object.type === "MemberExpression", + "Expected JSX member expression to be an identifier or nested member expression" + ); + const object = convertMemberExpressionToJsx(expr.object); + return t.jsxMemberExpression(object, property); + } +} + function codegenLValue( pattern: Pattern | Place | SpreadPattern ): t.ArrayPattern | t.ObjectPattern | t.RestElement | t.Identifier { diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md index 3405615979..92d06e5dce 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md @@ -166,15 +166,6 @@ let moduleLocal = false; 20 | 21 | const j = function bar([quz, qux], ...args) {}; -[ReactForget] TodoError: (BuildHIR::lowerJsxElementName) Handle JSXMemberExpression tags - 17 | ; - 18 | ; -> 19 | ; - | ^^^^^^^^^^^^^^^^^^^ - 20 | - 21 | const j = function bar([quz, qux], ...args) {}; - 22 | - [ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params 19 | ; 20 | diff --git a/compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.expect.md new file mode 100644 index 0000000000..899564d65e --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.expect.md @@ -0,0 +1,38 @@ + +## Input + +```javascript +function Component(props) { + return ( + + + + ); +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = {t0}; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.js b/compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.js new file mode 100644 index 0000000000..40f36ac072 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/jsx-member-expression.js @@ -0,0 +1,7 @@ +function Component(props) { + return ( + + + + ); +}