From 80bdab74470d237883d5c88fba3baafb3b0d447a Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Mon, 27 Mar 2023 10:34:11 -0700 Subject: [PATCH] Support AssignmentPattern (default values in destructuring) "Supports" default values in destructuring (AssignmentPattern) by lowering to a ternary, even in the output. Examples: ```javascript // Input: const [x = 'default'] = y; // Output: const [t0] = y; const x = t0 === undefined ? 'default' : t0; ``` ```javascript // Input 2 const [{x} = makeObject()] = y; // Output 2 const [t0] = y; const {x} = t0 === undefined ? makeObject() : t0; ``` Note that this is how Babel lowers AssignmentPattern, so it isn't too bad. This should help avoid the need to update product code, even if the output isn't perfectly ideal. --- compiler/forget/src/HIR/BuildHIR.ts | 100 ++++++++++++++++++ .../destructuring-array-default.expect.md | 32 ++++++ .../compiler/destructuring-array-default.js | 4 + ...cturing-assignment-array-default.expect.md | 42 ++++++++ .../destructuring-assignment-array-default.js | 9 ++ .../destructuring-object-default.expect.md | 32 ++++++ .../compiler/destructuring-object-default.js | 4 + .../compiler/error.todo-kitchensink.expect.md | 16 --- 8 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.js create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.js create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 87afe579c1..819bd27811 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -2446,6 +2446,106 @@ function lowerAssignment( } return { kind: "LoadLocal", place: temporary, loc: value.loc }; } + case "AssignmentPattern": { + const lvalue = lvaluePath as NodePath; + const loc = lvalue.node.loc ?? GeneratedSource; + const temp = buildTemporaryPlace(builder, loc); + + const testBlock = builder.reserve("value"); + const continuationBlock = builder.reserve(builder.currentBlockKind()); + + const consequent = builder.enter("value", () => { + const defaultValue = lowerExpressionToTemporary( + builder, + lvalue.get("right") + ); + builder.push({ + id: makeInstructionId(0), + lvalue: buildTemporaryPlace(builder, loc), + value: { + kind: "StoreLocal", + lvalue: { kind: InstructionKind.Const, place: { ...temp } }, + value: { ...defaultValue }, + loc, + }, + loc, + }); + return { + kind: "goto", + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc, + }; + }); + + const alternate = builder.enter("value", () => { + builder.push({ + id: makeInstructionId(0), + lvalue: buildTemporaryPlace(builder, loc), + value: { + kind: "StoreLocal", + lvalue: { kind: InstructionKind.Const, place: { ...temp } }, + value: { ...value }, + loc, + }, + loc, + }); + return { + kind: "goto", + variant: GotoVariant.Break, + block: continuationBlock.id, + id: makeInstructionId(0), + loc, + }; + }); + builder.terminateWithContinuation( + { + kind: "ternary", + test: testBlock.id, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc, + }, + testBlock + ); + const undef = buildTemporaryPlace(builder, loc); + builder.push({ + id: makeInstructionId(0), + lvalue: { ...undef }, + value: { + kind: "Primitive", + value: undefined, + loc, + }, + loc, + }); + const test = buildTemporaryPlace(builder, loc); + builder.push({ + id: makeInstructionId(0), + lvalue: { ...test }, + value: { + kind: "BinaryExpression", + left: { ...value }, + operator: "===", + right: { ...undef }, + loc, + }, + loc, + }); + builder.terminateWithContinuation( + { + kind: "branch", + test: { ...test }, + consequent, + alternate, + id: makeInstructionId(0), + }, + continuationBlock + ); + + return lowerAssignment(builder, loc, kind, lvalue.get("left"), temp); + } default: { builder.errors.push({ reason: `(BuildHIR::lowerAssignment) Handle ${lvaluePath.type} assignments`, diff --git a/compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.expect.md new file mode 100644 index 0000000000..0fa78caecc --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +function Component(props) { + const [[x] = [foo()]] = props.y; + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const [t0] = props.y; + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? [foo()] : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const [x] = t1; + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.js b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.js new file mode 100644 index 0000000000..eb00011bc0 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-array-default.js @@ -0,0 +1,4 @@ +function Component(props) { + const [[x] = [foo()]] = props.y; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.expect.md new file mode 100644 index 0000000000..5b10ade0d0 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.expect.md @@ -0,0 +1,42 @@ + +## Input + +```javascript +function Component(props) { + let x; + if (props.cond) { + [[x] = [foo()]] = props.y; + } else { + x = props.fallback; + } + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + let x = undefined; + if (props.cond) { + const [t0] = props.y; + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? [foo()] : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + [x] = t1; + } else { + x = props.fallback; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.js b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.js new file mode 100644 index 0000000000..3c5acd33a9 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-assignment-array-default.js @@ -0,0 +1,9 @@ +function Component(props) { + let x; + if (props.cond) { + [[x] = [foo()]] = props.y; + } else { + x = props.fallback; + } + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.expect.md new file mode 100644 index 0000000000..d5c533958a --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.expect.md @@ -0,0 +1,32 @@ + +## Input + +```javascript +function Component(props) { + const { x: { y } = { y: "default" } } = props.y; + return y; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const { x: t0 } = props.y; + const c_0 = $[0] !== t0; + let t1; + if (c_0) { + t1 = t0 === undefined ? { y: "default" } : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const { y } = t1; + return y; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.js b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.js new file mode 100644 index 0000000000..f380862d34 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/destructuring-object-default.js @@ -0,0 +1,4 @@ +function Component(props) { + const { x: { y } = { y: "default" } } = props.y; + return y; +} 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 144b6536d1..f099a8347e 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 @@ -79,13 +79,6 @@ let moduleLocal = false; ## Error ``` -[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle AssignmentPattern assignments -> 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) { - | ^^^^^^^ - 2 | let i = 0; - 3 | var x = []; - 4 | - [ReactForget] TodoError: (BuildHIR::lower) Handle AssignmentPattern params > 1 | function foo([a, b], { c, d, e = "e" }, f = "f", ...args) { | ^^^^^^^ @@ -127,15 +120,6 @@ let moduleLocal = false; 14 | 15 | ; -[ReactForget] TodoError: (BuildHIR::lowerAssignment) Handle AssignmentPattern assignments - 11 | - 12 | const g = { b() {}, c: () => {} }; -> 13 | const { z, aa = "aa" } = useCustom(); - | ^^^^^^^^^ - 14 | - 15 | ; - 16 | ; - [ReactForget] TodoError: (BuildHIR::lowerJsxElement) Handle JSXEmptyExpression expressions 14 | 15 | ;