mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(minifier): preserve lexical initializers in dead for loops (#27676)
Removing a `for` loop whose condition is always false currently discards `let`/`const` initializers.
For example:
```js
function f() {
for(let x = console.log('effect'); false;) { }
}
f();
```
loses its observable log.
Preserve the lexical declaration in a block that reuses the loop's scope, including initializer evaluation, destructuring effects, and TDZ behavior. Continue hoisting body `var` declarations and record discarded body/test/update references without dropping the moved initializer.
Regression coverage exercises both declaration kinds, initializer order, shadowing, direct eval, TDZ, destructuring, conditional statement placement, and body var hoisting. The test helper also checks idempotency.
This commit is contained in:
1 parent
a9e49ef40a
commit
a2534c63b7
2 files changed
+70
No files matched your search
@@ -198,6 +198,37 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
match test_boolean {
|
||||
Some(false) => match &for_stmt.init {
|
||||
Some(ForStatementInit::VariableDeclaration(decl)) if !decl.kind.is_var() => {
|
||||
ctx.replace_statement_with(stmt, |stmt, ctx| {
|
||||
let Statement::ForStatement(for_stmt) = stmt else { unreachable!() };
|
||||
let scope_id = for_stmt.scope_id();
|
||||
let ForStatement { span, init, test, update, body, .. } = for_stmt.unbox();
|
||||
let Some(ForStatementInit::VariableDeclaration(decl)) = init else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut keep_var = KeepVar::new();
|
||||
keep_var.visit_statement(&body);
|
||||
ctx.drop_statement(&body);
|
||||
if let Some(test) = test {
|
||||
ctx.drop_expression(&test);
|
||||
}
|
||||
if let Some(update) = update {
|
||||
ctx.drop_expression(&update);
|
||||
}
|
||||
let mut statements =
|
||||
ArenaVec::from_array_in([Statement::VariableDeclaration(decl)], ctx);
|
||||
if let Some(var_stmt) =
|
||||
keep_var.get_variable_declaration_statement(&ctx.ast)
|
||||
{
|
||||
statements.push(var_stmt);
|
||||
}
|
||||
// The initializer still runs, including destructuring effects and TDZ
|
||||
// checks. Reuse the loop scope to keep its lexical bindings isolated.
|
||||
Statement::new_block_statement_with_scope_id(
|
||||
span, statements, scope_id, ctx,
|
||||
)
|
||||
});
|
||||
}
|
||||
Some(ForStatementInit::VariableDeclaration(_)) => {
|
||||
let mut keep_var = KeepVar::new();
|
||||
keep_var.visit_statement(&for_stmt.body);
|
||||
|
||||
@@ -89,6 +89,45 @@ fn test_fold_useless_for() {
|
||||
// test("l1:for(;false;) { }", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_dead_for_lexical_initializer() {
|
||||
for kind in ["let", "const"] {
|
||||
test_unused(
|
||||
&format!("function f(){{for({kind} x=console.log('effect');false;){{}}}} f();"),
|
||||
"function f(){console.log('effect')} f();",
|
||||
);
|
||||
test(
|
||||
&format!("for ({kind} x = foo(); false; update()) {{ body(); }}"),
|
||||
"{ let x = foo(); }",
|
||||
);
|
||||
test(
|
||||
&format!("for ({kind} x = foo(), y = bar(x); false;) {{}}"),
|
||||
"{ let x = foo(), y = bar(x); }",
|
||||
);
|
||||
test(
|
||||
&format!("let x = 1; for ({kind} x = foo(); false;) {{}} use(x);"),
|
||||
"let x = 1; { let x = foo(); } use(1);",
|
||||
);
|
||||
test(
|
||||
&format!("for ({kind} x = eval('x'); false;) {{}}"),
|
||||
&format!("{{ {kind} x = eval('x'); }}"),
|
||||
);
|
||||
test(&format!("for ({kind} x = x; false;) {{}}"), "{ let x = x; }");
|
||||
test(&format!("if (a) for ({kind} x = foo(); false;) {{}}"), "if (a) { let x = foo(); }");
|
||||
test(
|
||||
&format!("for ({kind} x = foo(); false;) {{ var y = bar(); }} use(y);"),
|
||||
"{ let x = foo(); var y; } use(y);",
|
||||
);
|
||||
// Destructuring can invoke getters, iterators, and default initializers.
|
||||
for pattern in ["{x}", "[x]", "{x = foo()}", "[x = foo()]"] {
|
||||
test_unused(
|
||||
&format!("for ({kind} {pattern} = source; false;) {{}}"),
|
||||
&format!("{{ let {pattern} = source; }}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_minimize_loop_with_constant_condition_vanilla_for() {
|
||||
test("for(;true;) foo()", "for(;;) foo()");
|
||||
|
||||
Reference in new issue
Block a user