evanw / evanw/esbuild

Function declaration in a switch case is lowered to a TDZ-bound let initializer

Open
#4,533 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
40.1k
Forks
1.3k
PR merge metrics
No merged PRs in 30d

Description

esbuild changes the initialization timing of a block-level function declaration inside a `switch` statement.

Version:

```text
0.28.2
```

Input:

```js
switch (0) {
case 0: console.log(g()); // fn
case 1: function g() { return "fn"; }
}
```

This reproduces without minification, and esbuild outputs:

```js
switch (0) {
case 0:
console.log(g2()); // ReferenceError: Cannot access 'g2' before initialization
case 1:
let g2 = function() {
return "fn";
};
var g = g2;
}
```

Playground reproduction:

https://esbuild.github.io/try/#YgAwLjI4LjIAe21pbmlmeTogZmFsc2V9AGUAZW50cnkuanMAc3dpdGNoICgwKSB7CiAgY2FzZSAwOiBjb25zb2xlLmxvZyhnKCkpOwogIGNhc2UgMTogZnVuY3Rpb24gZygpIHsgcmV0dXJuICJmbiI7IH0KfQ

Enabling `minify` still produces a incorrect transformation:

```js
{console.log(c());let c=function(){return"fn"};var g=c} // ReferenceError: Cannot access 'c' before initialization
```
Playground reproduction:

https://esbuild.github.io/try/#YgAwLjI4LjIAe21pbmlmeTogdHJ1ZX0AZQBlbnRyeS5qcwBzd2l0Y2ggKDApIHsKICBjYXNlIDA6IGNvbnNvbGUubG9nKGcoKSk7CiAgY2FzZSAxOiBmdW5jdGlvbiBnKCkgeyByZXR1cm4gImZuIjsgfQp9

Expected output:

```js
{console.log(c()); function c(){return"fn"} }
```

A function declaration and a `let` declaration with a function-expression initializer do not have the same initialization timing.

For the original function declaration, the binding is initialized with its function object by `BlockDeclarationInstantiation` when entering the entire `CaseBlock`, before any case is selected.

For the transformed declaration, the binding is initialized only when execution reaches that statement. Since `case 0` is selected, execution never reaches the initializer in `case 1`, and the binding remains in the temporal dead zone.

The transform should preserve the function declaration or otherwise initialize the function binding before case selection.

Specification references:

- https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-switch-statement-runtime-semantics-evaluation
- https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-blockdeclarationinstantiation

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the two Playground reproductions and compare the non-minified and minified switch outputs with the referenced Switch Statement and BlockDeclarationInstantiation specification sections. Done means the function binding is initialized before case selection, so both reproductions call the function without a temporal-dead-zone error while preserving the expected output.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, javascript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.