@samitouri / QOS-React-2 / commits / ec0abcd643

[hir] Strip JSXEmptyExpression syntax

`JSXEmptyExpression` is never added to a React element's children [in `react.buildChildren`](https://github.com/babel/babel/blob/main/packages/babel-types/src/builders/react/buildChildren.ts), which is [used by](https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts#L649) `plugin-transform-react-jsx`]. An alternative would be to represent JSX expressions differently in HIR, then codegen `JSXEmptyExpression`s back when we encounter an `EmptyExpression` ```js - children: Array<Place>, - children: Array<Place | "EmptyExpression">, ``` (We could also retain `JSXEmptyExpression` as an `InstructionValue` that produces a Primitive. However, this would make babel types in Codegen a bit more messy, as `JSXEmptyExpression` does not extend `Expression` (which currently is the result of every `InstructionValue`).)

mofeiZ committed Mar 27, 2023 at 18:12 UTC ec0abcd64308b1268bceb205cbdb833114863c71
4 files changed +61 -27
compiler/forget/src/HIR/BuildHIR.ts
+19 -18
@@ -1352,9 +1352,10 @@ function lowerExpression(
1352 const expr = exprPath as NodePath<t.JSXElement>;
1353 const opening = expr.get("openingElement");
1354 const tag = lowerJsxElementName(builder, opening.get("name"));
1355 - const children = expr
1355 + const children: Array<Place> = expr
1356 .get("children")
1357 - .map((child) => lowerJsxElement(builder, child));
1357 + .map((child) => lowerJsxElement(builder, child))
1358 + .filter(notNull);
1359 const props: Array<JsxAttribute> = [];
1360 for (const attribute of opening.get("attributes")) {
1361 if (attribute.isJSXSpreadAttribute()) {
@@ -1426,9 +1427,10 @@ function lowerExpression(
1427 }
1428 case "JSXFragment": {
1429 const expr = exprPath as NodePath<t.JSXFragment>;
1429 - const children = expr
1430 + const children: Array<Place> = expr
1431 .get("children")
1431 - .map((child) => lowerJsxElement(builder, child));
1432 + .map((child) => lowerJsxElement(builder, child))
1433 + .filter(notNull);
1434 return {
1435 kind: "JsxFragment",
1436 children,
@@ -1950,27 +1952,22 @@ function lowerJsxElement(
1952 | t.JSXElement
1953 | t.JSXFragment
1954 >
1953 -): Place {
1955 +): Place | null {
1956 const exprNode = exprPath.node;
1957 const exprLoc = exprNode.loc ?? GeneratedSource;
1958 if (exprPath.isJSXElement() || exprPath.isJSXFragment()) {
1959 return lowerExpressionToTemporary(builder, exprPath);
1960 } else if (exprPath.isJSXExpressionContainer()) {
1961 const expression = exprPath.get("expression");
1960 - if (!expression.isExpression()) {
1961 - builder.errors.push({
1962 - reason: `(BuildHIR::lowerJsxElement) Handle ${expression.type} expressions`,
1963 - severity: ErrorSeverity.Todo,
1964 - nodePath: expression,
1965 - });
1966 - const place = lowerValueToTemporary(builder, {
1967 - kind: "UnsupportedNode",
1968 - node: exprNode,
1969 - loc: exprLoc,
1970 - });
1971 - return { ...place };
1962 + if (expression.isJSXEmptyExpression()) {
1963 + return null;
1964 + } else {
1965 + invariant(
1966 + expression.isExpression(),
1967 + `(BuildHIR::lowerJsxElement) Expected Expression but found ${expression.type}!`
1968 + );
1969 + return lowerExpressionToTemporary(builder, expression);
1970 }
1973 - return lowerExpressionToTemporary(builder, expression);
1971 } else if (exprPath.isJSXText()) {
1972 const place = lowerValueToTemporary(builder, {
1973 kind: "JSXText",
@@ -2465,3 +2462,7 @@ function gatherCapturedDeps(
2462
2463 return { identifiers: [...capturedIds], refs: [...capturedRefs] };
2464 }
2465 +
2466 +function notNull<T>(value: T | null): value is T {
2467 + return value !== null;
2468 +}
compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
-9
@@ -120,15 +120,6 @@ let moduleLocal = false;
120 14 |
121 15 | <Button haha={1}></Button>;
122
123 -[ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions
124 - 14 |
125 - 15 | <Button haha={1}></Button>;
126 -> 16 | <Button>{/** empty */}</Button>;
127 - | ^^^^^^^^^^^^
128 - 17 |
129 - 18 | const j = function bar([quz, qux], ...args) {};
130 - 19 |
131 -
123 [ReactForget] TodoError: (BuildHIR::lower) Handle RestElement params
124 16 | <Button>{/** empty */}</Button>;
125 17 |
compiler/forget/src/__tests__/fixtures/compiler/jsx-empty-expression.expect.md new
+34
@@ -0,0 +1,34 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +export function Component(props) {
6 + return (
7 + <div>
8 + {}
9 + {props.a}
10 + </div>
11 + );
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +export function Component(props) {
20 + const $ = React.unstable_useMemoCache(2);
21 + const c_0 = $[0] !== props.a;
22 + let t0;
23 + if (c_0) {
24 + t0 = <div>{props.a}</div>;
25 + $[0] = props.a;
26 + $[1] = t0;
27 + } else {
28 + t0 = $[1];
29 + }
30 + return t0;
31 +}
32 +
33 +```
34 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/jsx-empty-expression.js new
+8
@@ -0,0 +1,8 @@
1 +export function Component(props) {
2 + return (
3 + <div>
4 + {}
5 + {props.a}
6 + </div>
7 + );
8 +}