Skip to content

Commit

Permalink
fix #1425: super inside arrow inside lowered async
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 15, 2021
1 parent 68e369d commit b332f93
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 11 deletions.
34 changes: 34 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,39 @@
# Changelog

## Unreleased

* Fix `super` inside arrow function inside lowered `async` function ([#1425](https://github.com/evanw/esbuild/issues/1425))

When an `async` function is transformed into a regular function for target environments that don't support `async` such as `--target=es6`, references to `super` inside that function must be transformed too since the `async`-to-regular function transformation moves the function body into a nested function, so the `super` references are no longer syntactically valid. However, this transform didn't handle an edge case and `super` references inside of an arrow function were overlooked. This release fixes this bug:

```js
// Original code
class Foo extends Bar {
async foo() {
return () => super.foo()
}
}

// Old output (with --target=es6)
class Foo extends Bar {
foo() {
return __async(this, null, function* () {
return () => super.foo();
});
}
}

// New output (with --target=es6)
class Foo extends Bar {
foo() {
var __super = (key) => super[key];
return __async(this, null, function* () {
return () => __super("foo").call(this);
});
}
}
```

## 0.13.7

* Minify CSS alpha values correctly ([#1682](https://github.com/evanw/esbuild/issues/1682))
Expand Down
42 changes: 42 additions & 0 deletions internal/bundler/bundler_lower_test.go
Expand Up @@ -920,6 +920,43 @@ obj-method.js: error: Transforming object literal extensions to the configured t
})
}

func TestLowerAsyncSuperES2017NoBundle(t *testing.T) {
lower_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
class Derived extends Base {
async test(key) {
return [
await super.foo,
await super[key],
await super.foo.name,
await super[key].name,
await super.foo?.name,
await super[key]?.name,
await super.foo(1, 2),
await super[key](1, 2),
await super.foo?.(1, 2),
await super[key]?.(1, 2),
await (() => super.foo)(),
await (() => super[key])(),
await (() => super.foo())(),
await (() => super[key]())(),
]
}
}
`,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
UnsupportedJSFeatures: es(2017),
AbsOutputFile: "/out.js",
},
})
}

func TestLowerAsyncSuperES2016NoBundle(t *testing.T) {
lower_suite.expectBundled(t, bundled{
files: map[string]string{
Expand All @@ -939,6 +976,11 @@ func TestLowerAsyncSuperES2016NoBundle(t *testing.T) {
await super[key](1, 2),
await super.foo?.(1, 2),
await super[key]?.(1, 2),
await (() => super.foo)(),
await (() => super[key])(),
await (() => super.foo())(),
await (() => super[key]())(),
]
}
}
Expand Down
31 changes: 30 additions & 1 deletion internal/bundler/snapshots/snapshots_lower.txt
Expand Up @@ -109,12 +109,41 @@ class Derived extends Base {
yield __super("foo").call(this, 1, 2),
yield __super(key).call(this, 1, 2),
yield (_c = __super("foo")) == null ? void 0 : _c.call(this, 1, 2),
yield (_d = __super(key)) == null ? void 0 : _d.call(this, 1, 2)
yield (_d = __super(key)) == null ? void 0 : _d.call(this, 1, 2),
yield (() => __super("foo"))(),
yield (() => __super(key))(),
yield (() => __super("foo").call(this))(),
yield (() => __super(key).call(this))()
];
});
}
}

================================================================================
TestLowerAsyncSuperES2017NoBundle
---------- /out.js ----------
class Derived extends Base {
async test(key) {
var _a, _b, _c, _d;
return [
await super.foo,
await super[key],
await super.foo.name,
await super[key].name,
await ((_a = super.foo) == null ? void 0 : _a.name),
await ((_b = super[key]) == null ? void 0 : _b.name),
await super.foo(1, 2),
await super[key](1, 2),
await ((_c = super.foo) == null ? void 0 : _c.call(this, 1, 2)),
await ((_d = super[key]) == null ? void 0 : _d.call(this, 1, 2)),
await (() => super.foo)(),
await (() => super[key])(),
await (() => super.foo())(),
await (() => super[key]())()
];
}
}

================================================================================
TestLowerAsyncThis2016CommonJS
---------- /out.js ----------
Expand Down
6 changes: 4 additions & 2 deletions internal/js_parser/js_parser.go
Expand Up @@ -494,8 +494,6 @@ type fnOrArrowDataParse struct {
// restored on the call stack around code that parses nested functions and
// arrow expressions.
type fnOrArrowDataVisit struct {
superIndexRef *js_ast.Ref

isArrow bool
isAsync bool
isGenerator bool
Expand All @@ -513,6 +511,9 @@ type fnOrArrowDataVisit struct {
// restored on the call stack around code that parses nested functions (but not
// nested arrow functions).
type fnOnlyDataVisit struct {
superIndexRef *js_ast.Ref
shouldLowerSuper bool

// This is a reference to the magic "arguments" variable that exists inside
// functions in JavaScript. It will be non-nil inside functions and nil
// otherwise.
Expand Down Expand Up @@ -12813,6 +12814,7 @@ func (p *parser) visitFn(fn *js_ast.Fn, scopeLoc logger.Loc) {
isThisNested: true,
isNewTargetAllowed: true,
argumentsRef: &fn.ArgumentsRef,
shouldLowerSuper: fn.IsAsync && p.options.unsupportedJSFeatures.Has(compat.AsyncAwait),
}

if fn.Name != nil {
Expand Down
16 changes: 8 additions & 8 deletions internal/js_parser/js_parser_lower.go
Expand Up @@ -443,12 +443,12 @@ func (p *parser) lowerFunction(
returnStmt := js_ast.Stmt{Loc: bodyLoc, Data: &js_ast.SReturn{ValueOrNil: callAsync}}

// Prepend the "super" index function if necessary
if p.fnOrArrowDataVisit.superIndexRef != nil {
if p.fnOnlyDataVisit.superIndexRef != nil {
argRef := p.newSymbol(js_ast.SymbolOther, "key")
p.currentScope.Generated = append(p.currentScope.Generated, *p.fnOrArrowDataVisit.superIndexRef, argRef)
p.currentScope.Generated = append(p.currentScope.Generated, *p.fnOnlyDataVisit.superIndexRef, argRef)
superIndexStmt := js_ast.Stmt{Loc: bodyLoc, Data: &js_ast.SLocal{
Decls: []js_ast.Decl{{
Binding: js_ast.Binding{Loc: bodyLoc, Data: &js_ast.BIdentifier{Ref: *p.fnOrArrowDataVisit.superIndexRef}},
Binding: js_ast.Binding{Loc: bodyLoc, Data: &js_ast.BIdentifier{Ref: *p.fnOnlyDataVisit.superIndexRef}},
ValueOrNil: js_ast.Expr{Loc: bodyLoc, Data: &js_ast.EArrow{
Args: []js_ast.Arg{{
Binding: js_ast.Binding{Loc: bodyLoc, Data: &js_ast.BIdentifier{Ref: argRef}},
Expand Down Expand Up @@ -2729,21 +2729,21 @@ func (p *parser) lowerTemplateLiteral(loc logger.Loc, e *js_ast.ETemplate) js_as
}

func (p *parser) shouldLowerSuperPropertyAccess(expr js_ast.Expr) bool {
if p.fnOrArrowDataVisit.isAsync && p.options.unsupportedJSFeatures.Has(compat.AsyncAwait) {
if p.fnOnlyDataVisit.shouldLowerSuper {
_, isSuper := expr.Data.(*js_ast.ESuper)
return isSuper
}
return false
}

func (p *parser) lowerSuperPropertyAccess(loc logger.Loc, key js_ast.Expr) js_ast.Expr {
if p.fnOrArrowDataVisit.superIndexRef == nil {
if p.fnOnlyDataVisit.superIndexRef == nil {
ref := p.newSymbol(js_ast.SymbolOther, "__super")
p.fnOrArrowDataVisit.superIndexRef = &ref
p.fnOnlyDataVisit.superIndexRef = &ref
}
p.recordUsage(*p.fnOrArrowDataVisit.superIndexRef)
p.recordUsage(*p.fnOnlyDataVisit.superIndexRef)
return js_ast.Expr{Loc: loc, Data: &js_ast.ECall{
Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: *p.fnOrArrowDataVisit.superIndexRef}},
Target: js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: *p.fnOnlyDataVisit.superIndexRef}},
Args: []js_ast.Expr{key},
}}
}
Expand Down

0 comments on commit b332f93

Please sign in to comment.