Repository: swc-project/swc (rust)

Issue:
Left-hand side expressions of assignments are evaluated twice
### Describe the bug

In code like `o[getKey()] ??= "someValue"`, `getKey()` should be evaluated just once.

Instead, it is evaluated twice if the value is not set. If `getKey()` is not pure, this causes problems.

### Input code

```typescript
o[fn()] ??= true;
o[fn()] ||= true;
o[fn()] &&= true;
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.176&code=H4sIAAAAAAAAA22OQQuCQBCF7%2Fsr3m0UQ%2FKaLB06CnnpFh3KJllbdkDXg6j%2FvU0pCJrTg3nfx7PsYaCxzZV69K7yRhxq9gUPUYxRIVwlrhPLqZU6ovWH6mot3ynOl0bLvm8dksTkalbqDXhI0I6zkvNHd8E0afi253ztfKXlreEqDOlogzWnTx66SOK0EeMi2lAMrTUoI%2BxBZUHYgY7lCSGGET%2B2g%2FTOc7vqzMJl%2F6gXl5lWVfwAAAA%3D&config=H4sIAAAAAAAAA0WMTQrEIAxG75K1286id5hDBCctFv9IUhgR714tlu7C915ehUMsrBUyshCPS0pU%2FMMKZAOKZZcVTNf6tKEXagYUeScdiiyd%2BZSEJjUQXHRbGSWbQmYSeRHG3T9m66GQfucYKmjJdAc%2F0N7G%2FHPynaLySe0Cj9ke9LUAAAA%3D

### Expected behavior

The playground code compiled and executed should set the object key `1`, and call `getKey()` just once.

### Actual behavior

The key `2` is set, and `getKey()` is called twice.

### Version

1.2.176

### Additional context

Both Firefox and Node.js evaluate the left hand side of the assignment just once when using these operators directly.

It is also clear from the spec that it should be that way.

> 1\. Let lref be the result of evaluating [LeftHandSideExpression](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#prod-LeftHandSideExpression).
> [...]
> 6\. Perform ? [PutValue](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-putvalue)(lref, rval).

https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-assignment-operators

Pull request description:
fix(es/compat): memorize computed props in logical assignment
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**
closes #4521 

Patch:
diff --git a/crates/swc_ecma_ast/src/prop.rs b/crates/swc_ecma_ast/src/prop.rs
index f53478f84f..b75d0e2bb5 100644
--- a/crates/swc_ecma_ast/src/prop.rs
+++ b/crates/swc_ecma_ast/src/prop.rs
@@ -1,5 +1,5 @@
 use is_macro::Is;
-use swc_common::{ast_node, util::take::Take, EqIgnoreSpan, Span};
+use swc_common::{ast_node, util::take::Take, EqIgnoreSpan, Span, DUMMY_SP};
 
 use crate::{
     expr::Expr,
@@ -123,3 +123,12 @@ pub struct ComputedPropName {
     #[serde(rename = "expression")]
     pub expr: Box<Expr>,
 }
+
+impl Take for ComputedPropName {
+    fn dummy() -> Self {
+        Self {
+            span: DUMMY_SP,
+            expr: Take::dummy(),
+        }
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs b/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs
index 3869d64a4b..2b59ae2722 100644
--- a/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs
@@ -16,6 +16,35 @@ struct Operators {
     vars: Vec<VarDeclarator>,
 }
 
+impl Operators {
+    fn memorize_prop(&mut self, c: ComputedPropName) -> (ComputedPropName, ComputedPropName) {
+        let alias = alias_ident_for(&*c.expr, "_ref");
+        self.vars.push(VarDeclarator {
+            span: DUMMY_SP,
+            name: alias.clone().into(),
+            init: None,
+            definite: false,
+        });
+
+        (
+            ComputedPropName {
+                span: c.span,
+                expr: Expr::Assign(AssignExpr {
+                    span: DUMMY_SP,
+                    left: PatOrExpr::Pat(alias.clone().into()),
+                    op: op!("="),
+                    right: c.expr,
+                })
+                .into(),
+            },
+            ComputedPropName {
+                span: c.span,
+                expr: Box::new(alias.into()),
+            },
+        )
+    }
+}
+
 #[swc_trace]
 impl Parallel for Operators {
     fn create(&self) -> Self {
@@ -35,73 +64,115 @@ impl VisitMut for Operators {
     fn visit_mut_expr(&mut self, e: &mut Expr) {
         e.visit_mut_children_with(self);
 
-        match e {
-            Expr::Assign(AssignExpr {
-                span,
-                op: op @ (op!("&&=") | op!("||=") | op!("??=")),
-                left: PatOrExpr::Expr(left),
-                right,
-            }) if left.is_ident() || left.is_member() || left.is_super_prop() => {
-                let (left_expr, r_assign_target) = match &mut **left {
-                    Expr::Member(m) => {
-                        let alias = alias_ident_for(&m.obj, "_ref");
-                        self.vars.push(VarDeclarator {
+        if let Expr::Assign(AssignExpr {
+            span,
+            op: op @ (op!("&&=") | op!("||=") | op!("??=")),
+            left: PatOrExpr::Expr(left),
+            right,
+        }) = e
+        {
+            let (left_expr, r_assign_target) = match &mut **left {
+                Expr::SuperProp(SuperPropExpr {
+                    span,
+                    obj,
+                    prop: SuperProp::Computed(c),
+                }) => {
+                    let (left, right) = self.memorize_prop(c.take());
+
+                    (
+                        Box::new(
+                            SuperPropExpr {
+                                span: *span,
+                                obj: *obj,
+                                prop: SuperProp::Computed(left),
+                            }
+                            .into(),
+                        ),
+                        Box::new(
+                            SuperPropExpr {
+                                span: *span,
+                                obj: *obj,
+                                prop: SuperProp::Computed(right),
+                            }
+                            .into(),
+                        ),
+                    )
+                }
+                Expr::Member(m) => {
+                    let (left_obj, right_obj) = match *m.obj.take() {
+                        // TODO: local vars
+                        obj @ Expr::This(_) => (obj.clone().into(), obj.into()),
+                        obj => {
+                            let alias = alias_ident_for(&obj, "_ref");
+                            self.vars.push(VarDeclarator {
+                                span: DUMMY_SP,
+                                name: alias.clone().into(),
+                                init: None,
+                                definite: false,
+                            });
+
+                            (
+                                Box::new(Expr::Assign(AssignExpr {
+                                    span: DUMMY_SP,
+                                    op: op!("="),
+                                    left: PatOrExpr::Pat(alias.clone().into()),
+                                    right: obj.into(),
+                                })),
+                                Box::new(Expr::Ident(alias)),
+                            )
+                        }
+                    };
+
+                    let (left_prop, right_prop) = match m.prop.take() {
+                        MemberProp::PrivateName(_) => {
+                            unreachable!("private should be removed by class_properties")
+                        }
+                        MemberProp::Computed(c) => {
+                            let (left, right) = self.memorize_prop(c);
+                            (left.into(), right.into())
+                        }
+                        prop => (prop.clone(), prop),
+                    };
+
+                    (
+                        Box::new(Expr::Member(MemberExpr {
                             span: DUMMY_SP,
-                            name: alias.clone().into(),
-                            init: None,
-                            definite: false,
-                        });
-
-                        let left = m.clone();
-
-                        let obj = Box::new(Expr::Assign(AssignExpr {
+                            obj: left_obj,
+                            prop: left_prop,
+                        })),
+                        Box::new(Expr::Member(MemberExpr {
                             span: DUMMY_SP,
-                            op: op!("="),
-                            left: PatOrExpr::Pat(alias.clone().into()),
-                            right: m.obj.take(),
-                        }));
-
-                        (
-                            Box::new(Expr::Member(MemberExpr {
-                                span: DUMMY_SP,
-                                obj,
-                                ..left.clone()
-                            })),
-                            Box::new(Expr::Member(MemberExpr {
-                                span: DUMMY_SP,
-                                obj: Box::new(Expr::Ident(alias)),
-                                ..left
-                            })),
-                        )
-                    }
-                    _ => {
-                        let expr = left.take();
-                        (expr.clone(), expr)
-                    }
-                };
-
-                let right = Box::new(Expr::Assign(AssignExpr {
-                    span: DUMMY_SP,
-                    op: op!("="),
-                    left: PatOrExpr::Expr(r_assign_target),
-                    right: right.take(),
-                }));
-
-                let op = match *op {
-                    op!("??=") => op!("??"),
-                    op!("&&=") => op!("&&"),
-                    op!("||=") => op!("||"),
-                    _ => unreachable!(),
-                };
-
-                *e = Expr::Bin(BinExpr {
-                    span: *span,
-                    op,
-                    left: left_expr,
-                    right,
-                });
-            }
-            _ => {}
+                            obj: right_obj,
+                            prop: right_prop,
+                        })),
+                    )
+                }
+                _ => {
+                    let expr = left.take();
+                    (expr.clone(), expr)
+                }
+            };
+
+            let right = Box::new(Expr::Assign(AssignExpr {
+                span: DUMMY_SP,
+                op: op!("="),
+                left: PatOrExpr::Expr(r_assign_target),
+                right: right.take(),
+            }));
+
+            let op = match *op {
+                op!("??=") => op!("??"),
+                op!("&&=") => op!("&&"),
+                op!("||=") => op!("||"),
+                _ => unreachable!(),
+            };
+
+            *e = Expr::Bin(BinExpr {
+                span: *span,
+                op,
+                left: left_expr,
+                right,
+            });
         }
     }
 


Test patch:
diff --git a/crates/swc/tests/tsc-references/logicalAssignment10_es2015.1.normal.js b/crates/swc/tests/tsc-references/logicalAssignment10_es2015.1.normal.js
index 54ebfce98e..2e7c71afbc 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment10_es2015.1.normal.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment10_es2015.1.normal.js
@@ -1,4 +1,4 @@
-var _obj, _ref;
+var _obj, _ref, _ref1, _ref2;
 // @target: esnext, es2021, es2020, es2015
 var count = 0;
 var obj = {};
@@ -9,6 +9,6 @@ const oobj = {
     obj
 };
 var ref;
-(ref = (_obj = obj)[incr()]) !== null && ref !== void 0 ? ref : _obj[incr()] = incr();
+(ref = (_obj = obj)[_ref = incr()]) !== null && ref !== void 0 ? ref : _obj[_ref] = incr();
 var ref1;
-(ref1 = (_ref = oobj["obj"])[incr()]) !== null && ref1 !== void 0 ? ref1 : _ref[incr()] = incr();
+(ref1 = (_ref1 = oobj["obj"])[_ref2 = incr()]) !== null && ref1 !== void 0 ? ref1 : _ref1[_ref2] = incr();
diff --git a/crates/swc/tests/tsc-references/logicalAssignment10_es2015.2.minified.js b/crates/swc/tests/tsc-references/logicalAssignment10_es2015.2.minified.js
index f52fd8e1ed..a9e0cbd2b2 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment10_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment10_es2015.2.minified.js
@@ -1,7 +1,7 @@
-var _obj, _ref, ref, ref1, count = 0, obj = {};
+var _obj, _ref, _ref1, _ref2, ref, ref1, count = 0, obj = {};
 function incr() {
     return ++count;
 }
-null !== (ref = (_obj = obj)[incr()]) && void 0 !== ref || (_obj[incr()] = incr()), null !== (ref1 = (_ref = ({
+null !== (ref = (_obj = obj)[_ref = incr()]) && void 0 !== ref || (_obj[_ref] = incr()), null !== (ref1 = (_ref1 = ({
     obj
-}).obj)[incr()]) && void 0 !== ref1 || (_ref[incr()] = incr());
+}).obj)[_ref2 = incr()]) && void 0 !== ref1 || (_ref1[_ref2] = incr());
diff --git a/crates/swc/tests/tsc-references/logicalAssignment10_es5.1.normal.js b/crates/swc/tests/tsc-references/logicalAssignment10_es5.1.normal.js
index 0559ddafc7..5d63adc513 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment10_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment10_es5.1.normal.js
@@ -1,4 +1,4 @@
-var _obj, _ref;
+var _obj, _ref, _ref1, _ref2;
 // @target: esnext, es2021, es2020, es2015
 var count = 0;
 var obj = {};
@@ -9,6 +9,6 @@ var oobj = {
     obj: obj
 };
 var ref;
-(ref = (_obj = obj)[incr()]) !== null && ref !== void 0 ? ref : _obj[incr()] = incr();
+(ref = (_obj = obj)[_ref = incr()]) !== null && ref !== void 0 ? ref : _obj[_ref] = incr();
 var ref1;
-(ref1 = (_ref = oobj["obj"])[incr()]) !== null && ref1 !== void 0 ? ref1 : _ref[incr()] = incr();
+(ref1 = (_ref1 = oobj["obj"])[_ref2 = incr()]) !== null && ref1 !== void 0 ? ref1 : _ref1[_ref2] = incr();
diff --git a/crates/swc/tests/tsc-references/logicalAssignment10_es5.2.minified.js b/crates/swc/tests/tsc-references/logicalAssignment10_es5.2.minified.js
index fe3245b293..7eb089fd0c 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment10_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment10_es5.2.minified.js
@@ -1,5 +1,5 @@
-var _obj, _ref, ref, ref1, count = 0, obj = {};
+var _obj, _ref, _ref1, _ref2, ref, ref1, count = 0, obj = {};
 function incr() {
     return ++count;
 }
-null !== (ref = (_obj = obj)[incr()]) && void 0 !== ref || (_obj[incr()] = incr()), null !== (ref1 = (_ref = obj)[incr()]) && void 0 !== ref1 || (_ref[incr()] = incr());
+null !== (ref = (_obj = obj)[_ref = incr()]) && void 0 !== ref || (_obj[_ref] = incr()), null !== (ref1 = (_ref1 = obj)[_ref2 = incr()]) && void 0 !== ref1 || (_ref1[_ref2] = incr());
diff --git a/crates/swc/tests/tsc-references/logicalAssignment2_es2015.1.normal.js b/crates/swc/tests/tsc-references/logicalAssignment2_es2015.1.normal.js
index 199c62c179..8b5e5acfbc 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment2_es2015.1.normal.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment2_es2015.1.normal.js
@@ -1,13 +1,13 @@
-var _a, _b, _c, _foo, _foo1, _foo2, _ref, _ref1, _ref2;
+var _a, _b, _c, _foo, _ref, _foo1, _ref1, _foo2, _ref2, _ref3, _ref4, _ref5;
 (_a = a).baz && (_a.baz = result.baz);
 (_b = b).baz || (_b.baz = result.baz);
 var _baz;
 (_baz = (_c = c).baz) !== null && _baz !== void 0 ? _baz : _c.baz = result.baz;
-(_foo = a.foo)["baz"] && (_foo["baz"] = result.foo.baz);
-(_foo1 = b.foo)["baz"] || (_foo1["baz"] = result.foo.baz);
+(_foo = a.foo)[_ref = "baz"] && (_foo[_ref] = result.foo.baz);
+(_foo1 = b.foo)[_ref1 = "baz"] || (_foo1[_ref1] = result.foo.baz);
 var ref;
-(ref = (_foo2 = c.foo)["baz"]) !== null && ref !== void 0 ? ref : _foo2["baz"] = result.foo.baz;
-(_ref = a.foo.bar()).baz && (_ref.baz = result.foo.bar().baz);
-(_ref1 = b.foo.bar()).baz || (_ref1.baz = result.foo.bar().baz);
+(ref = (_foo2 = c.foo)[_ref2 = "baz"]) !== null && ref !== void 0 ? ref : _foo2[_ref2] = result.foo.baz;
+(_ref3 = a.foo.bar()).baz && (_ref3.baz = result.foo.bar().baz);
+(_ref4 = b.foo.bar()).baz || (_ref4.baz = result.foo.bar().baz);
 var _baz1;
-(_baz1 = (_ref2 = c.foo.bar()).baz) !== null && _baz1 !== void 0 ? _baz1 : _ref2.baz = result.foo.bar().baz;
+(_baz1 = (_ref5 = c.foo.bar()).baz) !== null && _baz1 !== void 0 ? _baz1 : _ref5.baz = result.foo.bar().baz;
diff --git a/crates/swc/tests/tsc-references/logicalAssignment2_es2015.2.minified.js b/crates/swc/tests/tsc-references/logicalAssignment2_es2015.2.minified.js
index d19c3c2ff5..51080b3c45 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment2_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment2_es2015.2.minified.js
@@ -1,2 +1,2 @@
-var _a, _b, _c, _foo, _foo1, _foo2, _ref, _ref1, _ref2, _baz, ref, _baz1;
-(_a = a).baz && (_a.baz = result.baz), (_b = b).baz || (_b.baz = result.baz), null !== (_baz = (_c = c).baz) && void 0 !== _baz || (_c.baz = result.baz), (_foo = a.foo).baz && (_foo.baz = result.foo.baz), (_foo1 = b.foo).baz || (_foo1.baz = result.foo.baz), null !== (ref = (_foo2 = c.foo).baz) && void 0 !== ref || (_foo2.baz = result.foo.baz), (_ref = a.foo.bar()).baz && (_ref.baz = result.foo.bar().baz), (_ref1 = b.foo.bar()).baz || (_ref1.baz = result.foo.bar().baz), null !== (_baz1 = (_ref2 = c.foo.bar()).baz) && void 0 !== _baz1 || (_ref2.baz = result.foo.bar().baz);
+var _a, _b, _c, _foo, _ref, _foo1, _ref1, _foo2, _ref2, _ref3, _ref4, _ref5, _baz, ref, _baz1;
+(_a = a).baz && (_a.baz = result.baz), (_b = b).baz || (_b.baz = result.baz), null !== (_baz = (_c = c).baz) && void 0 !== _baz || (_c.baz = result.baz), (_foo = a.foo)[_ref = "baz"] && (_foo[_ref] = result.foo.baz), (_foo1 = b.foo)[_ref1 = "baz"] || (_foo1[_ref1] = result.foo.baz), null !== (ref = (_foo2 = c.foo)[_ref2 = "baz"]) && void 0 !== ref || (_foo2[_ref2] = result.foo.baz), (_ref3 = a.foo.bar()).baz && (_ref3.baz = result.foo.bar().baz), (_ref4 = b.foo.bar()).baz || (_ref4.baz = result.foo.bar().baz), null !== (_baz1 = (_ref5 = c.foo.bar()).baz) && void 0 !== _baz1 || (_ref5.baz = result.foo.bar().baz);
diff --git a/crates/swc/tests/tsc-references/logicalAssignment2_es5.1.normal.js b/crates/swc/tests/tsc-references/logicalAssignment2_es5.1.normal.js
index 199c62c179..8b5e5acfbc 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment2_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment2_es5.1.normal.js
@@ -1,13 +1,13 @@
-var _a, _b, _c, _foo, _foo1, _foo2, _ref, _ref1, _ref2;
+var _a, _b, _c, _foo, _ref, _foo1, _ref1, _foo2, _ref2, _ref3, _ref4, _ref5;
 (_a = a).baz && (_a.baz = result.baz);
 (_b = b).baz || (_b.baz = result.baz);
 var _baz;
 (_baz = (_c = c).baz) !== null && _baz !== void 0 ? _baz : _c.baz = result.baz;
-(_foo = a.foo)["baz"] && (_foo["baz"] = result.foo.baz);
-(_foo1 = b.foo)["baz"] || (_foo1["baz"] = result.foo.baz);
+(_foo = a.foo)[_ref = "baz"] && (_foo[_ref] = result.foo.baz);
+(_foo1 = b.foo)[_ref1 = "baz"] || (_foo1[_ref1] = result.foo.baz);
 var ref;
-(ref = (_foo2 = c.foo)["baz"]) !== null && ref !== void 0 ? ref : _foo2["baz"] = result.foo.baz;
-(_ref = a.foo.bar()).baz && (_ref.baz = result.foo.bar().baz);
-(_ref1 = b.foo.bar()).baz || (_ref1.baz = result.foo.bar().baz);
+(ref = (_foo2 = c.foo)[_ref2 = "baz"]) !== null && ref !== void 0 ? ref : _foo2[_ref2] = result.foo.baz;
+(_ref3 = a.foo.bar()).baz && (_ref3.baz = result.foo.bar().baz);
+(_ref4 = b.foo.bar()).baz || (_ref4.baz = result.foo.bar().baz);
 var _baz1;
-(_baz1 = (_ref2 = c.foo.bar()).baz) !== null && _baz1 !== void 0 ? _baz1 : _ref2.baz = result.foo.bar().baz;
+(_baz1 = (_ref5 = c.foo.bar()).baz) !== null && _baz1 !== void 0 ? _baz1 : _ref5.baz = result.foo.bar().baz;
diff --git a/crates/swc/tests/tsc-references/logicalAssignment2_es5.2.minified.js b/crates/swc/tests/tsc-references/logicalAssignment2_es5.2.minified.js
index d19c3c2ff5..51080b3c45 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment2_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment2_es5.2.minified.js
@@ -1,2 +1,2 @@
-var _a, _b, _c, _foo, _foo1, _foo2, _ref, _ref1, _ref2, _baz, ref, _baz1;
-(_a = a).baz && (_a.baz = result.baz), (_b = b).baz || (_b.baz = result.baz), null !== (_baz = (_c = c).baz) && void 0 !== _baz || (_c.baz = result.baz), (_foo = a.foo).baz && (_foo.baz = result.foo.baz), (_foo1 = b.foo).baz || (_foo1.baz = result.foo.baz), null !== (ref = (_foo2 = c.foo).baz) && void 0 !== ref || (_foo2.baz = result.foo.baz), (_ref = a.foo.bar()).baz && (_ref.baz = result.foo.bar().baz), (_ref1 = b.foo.bar()).baz || (_ref1.baz = result.foo.bar().baz), null !== (_baz1 = (_ref2 = c.foo.bar()).baz) && void 0 !== _baz1 || (_ref2.baz = result.foo.bar().baz);
+var _a, _b, _c, _foo, _ref, _foo1, _ref1, _foo2, _ref2, _ref3, _ref4, _ref5, _baz, ref, _baz1;
+(_a = a).baz && (_a.baz = result.baz), (_b = b).baz || (_b.baz = result.baz), null !== (_baz = (_c = c).baz) && void 0 !== _baz || (_c.baz = result.baz), (_foo = a.foo)[_ref = "baz"] && (_foo[_ref] = result.foo.baz), (_foo1 = b.foo)[_ref1 = "baz"] || (_foo1[_ref1] = result.foo.baz), null !== (ref = (_foo2 = c.foo)[_ref2 = "baz"]) && void 0 !== ref || (_foo2[_ref2] = result.foo.baz), (_ref3 = a.foo.bar()).baz && (_ref3.baz = result.foo.bar().baz), (_ref4 = b.foo.bar()).baz || (_ref4.baz = result.foo.bar().baz), null !== (_baz1 = (_ref5 = c.foo.bar()).baz) && void 0 !== _baz1 || (_ref5.baz = result.foo.bar().baz);
diff --git a/crates/swc/tests/tsc-references/logicalAssignment3_es2015.1.normal.js b/crates/swc/tests/tsc-references/logicalAssignment3_es2015.1.normal.js
index 729f862f24..f2a823cecd 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment3_es2015.1.normal.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment3_es2015.1.normal.js
@@ -1,4 +1,4 @@
-a.baz &&= result.baz;
-b.baz ||= result.baz;
-var ref$;
-ref$ = (ref$ = c.baz) !== null && ref$ !== void 0 ? ref$ : c.baz = result.baz;
+a.baz && (a.baz = result.baz);
+b.baz || (b.baz = result.baz);
+var ref;
+(ref = c.baz) !== null && ref !== void 0 ? ref : c.baz = result.baz;
diff --git a/crates/swc/tests/tsc-references/logicalAssignment3_es2015.2.minified.js b/crates/swc/tests/tsc-references/logicalAssignment3_es2015.2.minified.js
index 715c8d3b2b..057aa4fb93 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment3_es2015.2.minified.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment3_es2015.2.minified.js
@@ -1,2 +1,2 @@
-var ref$;
-a.baz &&= result.baz, b.baz ||= result.baz, ref$ = null !== (ref$ = c.baz) && void 0 !== ref$ ? ref$ : c.baz = result.baz;
+var ref;
+a.baz && (a.baz = result.baz), b.baz || (b.baz = result.baz), null !== (ref = c.baz) && void 0 !== ref || (c.baz = result.baz);
diff --git a/crates/swc/tests/tsc-references/logicalAssignment3_es5.1.normal.js b/crates/swc/tests/tsc-references/logicalAssignment3_es5.1.normal.js
index 729f862f24..f2a823cecd 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment3_es5.1.normal.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment3_es5.1.normal.js
@@ -1,4 +1,4 @@
-a.baz &&= result.baz;
-b.baz ||= result.baz;
-var ref$;
-ref$ = (ref$ = c.baz) !== null && ref$ !== void 0 ? ref$ : c.baz = result.baz;
+a.baz && (a.baz = result.baz);
+b.baz || (b.baz = result.baz);
+var ref;
+(ref = c.baz) !== null && ref !== void 0 ? ref : c.baz = result.baz;
diff --git a/crates/swc/tests/tsc-references/logicalAssignment3_es5.2.minified.js b/crates/swc/tests/tsc-references/logicalAssignment3_es5.2.minified.js
index 715c8d3b2b..057aa4fb93 100644
--- a/crates/swc/tests/tsc-references/logicalAssignment3_es5.2.minified.js
+++ b/crates/swc/tests/tsc-references/logicalAssignment3_es5.2.minified.js
@@ -1,2 +1,2 @@
-var ref$;
-a.baz &&= result.baz, b.baz ||= result.baz, ref$ = null !== (ref$ = c.baz) && void 0 !== ref$ ? ref$ : c.baz = result.baz;
+var ref;
+a.baz && (a.baz = result.baz), b.baz || (b.baz = result.baz), null !== (ref = c.baz) && void 0 !== ref || (c.baz = result.baz);
diff --git a/crates/swc_ecma_transforms_compat/tests/es2021_logcal_assignments.rs b/crates/swc_ecma_transforms_compat/tests/es2021_logcal_assignments.rs
index ece493dc25..6efe48e9df 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2021_logcal_assignments.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2021_logcal_assignments.rs
@@ -1,6 +1,6 @@
 use swc_ecma_parser::{Syntax, TsConfig};
 use swc_ecma_transforms_compat::es2021;
-use swc_ecma_transforms_testing::test;
+use swc_ecma_transforms_testing::{test, test_exec};
 use swc_ecma_visit::Fold;
 
 fn tr() -> impl Fold {
@@ -80,3 +80,58 @@ test!(
     (_a = a).b ?? (_a.b = b);
     "
 );
+
+test_exec!(
+    syntax(),
+    |_| tr(),
+    null_coalescing,
+    "var x = undefined;
+    var sets = 0;
+    var obj = {
+      get x() {
+        return x;
+      },
+    
+      set x(value) {
+        sets++;
+        x = value;
+      },
+    };
+    
+    expect(obj.x ??= 1).toBe(1);
+    expect(sets).toBe(1);
+    expect(obj.x ??= 2).toBe(1);
+    expect(sets).toBe(1);
+    
+    var gets = 0;
+    var deep = {
+      get obj() {
+        gets++;
+        return obj;
+      },
+    };
+    
+    obj.x = undefined;
+    expect(deep.obj.x ??= 1).toBe(1);
+    expect(gets).toBe(1);
+    expect(deep.obj.x ??= 2).toBe(1);
+    expect(gets).toBe(2);
+    
+    var key = 0;
+    obj.x = undefined;
+    expect(obj[++key] ??= 1).toBe(1);
+    expect(key).toBe(1);
+    key = 0;
+    expect(obj[++key] ??= 2).toBe(1);
+    expect(key).toBe(1);
+    
+    obj.x = undefined;
+    key = 0;
+    expect(deep.obj[++key] ??= 1).toBe(1);
+    expect(gets).toBe(3);
+    expect(key).toBe(1);
+    key = 0;
+    expect(deep.obj[++key] ??= 2).toBe(1);
+    expect(gets).toBe(4);
+    expect(key).toBe(1);"
+);