Description
TypeScript Version: 4.0.2
Search Terms: async argument throw emit lower incorrect behavior
Code
(async() => {
async function f1(x, y = z) {}
async function f2({[z]: x}) {}
for (let f of [f1, f2]) {
let p = f({}) // This should not throw an error
try {
await p
} catch (e) {
continue // This error is expected
}
throw new Error('Expected an error')
}
})()
Expected behavior:
The code should run without crashing, both when compiled to ESNext
and when compiled to ES2015
.
Actual behavior:
The code crashes with ReferenceError: z is not defined
when compiled to ES2015
. This is because the generated code moves the evaluation of default arguments from inside the promise context to outside of the promise context during lowering:
function f1(x, y = z) {
return __awaiter(this, void 0, void 0, function* () { });
}
function f2({ [z]: x }) {
return __awaiter(this, void 0, void 0, function* () { });
}
There are many ways to fix this, but an example correct compilation might look something like this:
function f1(_0) {
return __awaiter(this, arguments, void 0, function* (x, y = z) { });
}
function f2(_0) {
return __awaiter(this, arguments, void 0, function* ({ [z]: x }) { });
}
The _0
variables are to ensure f1.length
and f2.length
don't change.
I discovered this correctness issue because I replicated TypeScript's approach to async function lowering into esbuild but it turned out to have this correctness issue.
Playground Link: link
Related Issues: