Skip to content

Commit

Permalink
overhaul computed property side effect handling
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed May 4, 2024
1 parent 5575fc2 commit 1f28305
Show file tree
Hide file tree
Showing 7 changed files with 460 additions and 248 deletions.
71 changes: 71 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,77 @@
};
```

* Fix an obscure bug with lowering class members with computed property keys

When class members that use newer syntax features are transformed for older target environments, they sometimes need to be relocated. However, care must be taken to not reorder any side effects caused by computed property keys. For example, the following code must evaluate `a()` then `b()` then `c()`:

```js
class Foo {
[a()]() {}
[b()];
static { c() }
}
```

Previously esbuild did this by shifting the computed property key _forward_ to the next spot in the evaluation order. Classes evaluate all computed keys first and then all static class elements, so if the last computed key needs to be shifted, esbuild previously inserted a static block at start of the class body, ensuring it came before all other static class elements:

```js
var _a;
class Foo {
constructor() {
__publicField(this, _a);
}
static {
_a = b();
}
[a()]() {
}
static {
c();
}
}
```

However, this could cause esbuild to accidentally generate a syntax error if the computed property key contains code that isn't allowed in a static block, such as an `await` expression. With this release, esbuild fixes this problem by shifting the computed property key _backward_ to the previous spot in the evaluation order instead, which may push it into the `extends` clause or even before the class itself:

```js
// Original code
class Foo {
[a()]() {}
[await b()];
static { c() }
}

// Old output (with --supported:class-field=false)
var _a;
class Foo {
constructor() {
__publicField(this, _a);
}
static {
_a = await b();
}
[a()]() {
}
static {
c();
}
}

// New output (with --supported:class-field=false)
var _a, _b;
class Foo {
constructor() {
__publicField(this, _a);
}
[(_b = a(), _a = await b(), _b)]() {
}
static {
c();
}
}
```

## 0.20.2

* Support TypeScript experimental decorators on `abstract` class fields ([#3684](https://github.com/evanw/esbuild/issues/3684))
Expand Down
96 changes: 48 additions & 48 deletions internal/bundler_tests/snapshots/snapshots_lower.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ class Foo {
set one(_) {
__privateSet(this, _one, _);
}
get [_a = three()]() {
get [_b = three()]() {
return __privateGet(this, _a2);
}
set [_a](_) {
set [_b](_) {
__privateSet(this, _a2, _);
}
static get four() {
Expand All @@ -45,10 +45,10 @@ class Foo {
static set four(_) {
__privateSet(this, _four, _);
}
static get [_b = six()]() {
static get [_a = six()]() {
return __privateGet(this, _b2);
}
static set [_b](_) {
static set [_a](_) {
__privateSet(this, _b2, _);
}
}
Expand Down Expand Up @@ -92,10 +92,10 @@ class Foo {
set one(_) {
__privateSet(this, _one, _);
}
get [_a = three()]() {
get [_b = three()]() {
return __privateGet(this, _a2);
}
set [_a](_) {
set [_b](_) {
__privateSet(this, _a2, _);
}
static get four() {
Expand All @@ -104,10 +104,10 @@ class Foo {
static set four(_) {
__privateSet(this, _four, _);
}
static get [_b = six()]() {
static get [_a = six()]() {
return __privateGet(this, _b2);
}
static set [_b](_) {
static set [_a](_) {
__privateSet(this, _b2, _);
}
}
Expand Down Expand Up @@ -203,10 +203,10 @@ class Foo {
set one(_) {
__privateSet(this, _one, _);
}
get [_a = three()]() {
get [_b = three()]() {
return __privateGet(this, _a2);
}
set [_a](_) {
set [_b](_) {
__privateSet(this, _a2, _);
}
static get four() {
Expand All @@ -215,10 +215,10 @@ class Foo {
static set four(_) {
__privateSet(this, _four, _);
}
static get [_b = six()]() {
static get [_a = six()]() {
return __privateGet(this, _b2);
}
static set [_b](_) {
static set [_a](_) {
__privateSet(this, _b2, _);
}
}
Expand Down Expand Up @@ -319,10 +319,10 @@ class Foo {
this.#_two = _;
}
#a = 3;
get [_a = three()]() {
get [_b = three()]() {
return this.#a;
}
set [_a](_) {
set [_b](_) {
this.#a = _;
}
static #four = 4;
Expand All @@ -340,10 +340,10 @@ class Foo {
this.#_five = _;
}
static #b = 6;
static get [_b = six()]() {
static get [_a = six()]() {
return this.#b;
}
static set [_b](_) {
static set [_a](_) {
this.#b = _;
}
}
Expand All @@ -366,10 +366,10 @@ class Foo {
this.#_two = _;
}
#a = 3;
get [_a = three()]() {
get [_b = three()]() {
return this.#a;
}
set [_a](_) {
set [_b](_) {
this.#a = _;
}
static #four = 4;
Expand All @@ -387,10 +387,10 @@ class Foo {
this.#_five = _;
}
static #b = 6;
static get [_b = six()]() {
static get [_a = six()]() {
return this.#b;
}
static set [_b](_) {
static set [_a](_) {
this.#b = _;
}
}
Expand Down Expand Up @@ -453,10 +453,10 @@ class Foo {
this.#_two = _;
}
#a = 3;
get [_a = three()]() {
get [_b = three()]() {
return this.#a;
}
set [_a](_) {
set [_b](_) {
this.#a = _;
}
static #four = 4;
Expand All @@ -474,10 +474,10 @@ class Foo {
this.#_five = _;
}
static #b = 6;
static get [_b = six()]() {
static get [_a = six()]() {
return this.#b;
}
static set [_b](_) {
static set [_a](_) {
this.#b = _;
}
}
Expand Down Expand Up @@ -1603,22 +1603,22 @@ class Derived2 extends Base {
constructor() {
super(...arguments);
__publicField(this, "b", () => __async(this, null, function* () {
var _a, _b;
return _b = class {
var _a;
return _a = __superGet(Derived2.prototype, this, "foo"), class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = __superGet(Derived2.prototype, this, "foo"), _b;
};
}));
}
a() {
return __async(this, null, function* () {
var _a, _b;
return _b = class {
var _a;
return _a = __superGet(Derived2.prototype, this, "foo"), class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = __superGet(Derived2.prototype, this, "foo"), _b;
};
});
}
}
Expand Down Expand Up @@ -1691,21 +1691,21 @@ class Derived2 extends Base {
constructor() {
super(...arguments);
__publicField(this, "b", async () => {
var _a, _b;
return _b = class {
var _a;
return _a = super.foo, class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = super.foo, _b;
};
});
}
async a() {
var _a, _b;
return _b = class {
var _a;
return _a = super.foo, class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = super.foo, _b;
};
}
}
for (let i = 0; i < 3; i++) {
Expand Down Expand Up @@ -3334,22 +3334,22 @@ let fn = () => __async(this, null, function* () {
const _Derived2 = class _Derived2 extends Base {
static a() {
return __async(this, null, function* () {
var _a, _b;
return _b = class {
var _a;
return _a = __superGet(_Derived2, this, "foo"), class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = __superGet(_Derived2, this, "foo"), _b;
};
});
}
};
__publicField(_Derived2, "b", () => __async(_Derived2, null, function* () {
var _a, _b;
return _b = class {
var _a;
return _a = __superGet(_Derived2, _Derived2, "foo"), class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = __superGet(_Derived2, _Derived2, "foo"), _b;
};
}));
let Derived2 = _Derived2;

Expand Down Expand Up @@ -3402,21 +3402,21 @@ let fn = async () => {
};
const _Derived2 = class _Derived2 extends Base {
static async a() {
var _a, _b;
return _b = class {
var _a;
return _a = super.foo, class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = super.foo, _b;
};
}
};
__publicField(_Derived2, "b", async () => {
var _a, _b;
return _b = class {
var _a;
return _a = __superGet(_Derived2, _Derived2, "foo"), class {
constructor() {
__publicField(this, _a, 123);
}
}, _a = __superGet(_Derived2, _Derived2, "foo"), _b;
};
});
let Derived2 = _Derived2;

Expand Down

0 comments on commit 1f28305

Please sign in to comment.