Function declaration in a switch case is lowered to a TDZ-bound let initializer
esbuild changes the initialization timing of a block-level function declaration inside a switch statement.
Version:
0.28.2Input:
switch (0) {
case 0: console.log(g()); // fn
case 1: function g() { return "fn"; }
}This reproduces without minification, and esbuild outputs:
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:
Enabling minify still produces a incorrect transformation:
{console.log(c());let c=function(){return"fn"};var g=c} // ReferenceError: Cannot access 'c' before initializationPlayground reproduction:
Expected output:
{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:
Source: evanw/esbuild