feat(minifier): invert !0 and !1 in place for boolean context to 1 and 0 (#26050)

improve negation of 1 and 0 to 0 and 1 in boolean context

previously only `!!0` and `!!1` could be inlined to `1` and `0` and now
`!0` and `!1` will also be inlined,

this change allows us to flip numeric values in-place instead of having
to first to create unary and remove it afterwards

```
!0 was converted before to 1 in some cases:
1. negate 1
2. !1 - add unary
3. 0 - remove unary

now:
1. negate 1
2. 0
```
This commit is contained in:
Armano authored and GitHub committed 2026-08-29 15:36:17 +08:00
1 parent 1e902cc1ad
commit 784e9faad2
8 files changed
+50 -40

No files matched your search

@@ -46,6 +46,12 @@ impl<'a> PeepholeOptimizations {
binary_expr.operator = binary_expr.operator.equality_inverse_operator().unwrap();
true
}
// `!0` => `1`
// `!1` => `0`
Expression::NumericLiteral(num) if boolean_context => {
num.value = if num.value.is_nan() || num.value == 0.0 { 1.0 } else { 0.0 };
true
}
// `!(a == b || c == d)` => `a != b && c != d`
// `!(a == b && c == d)` => `a != b || c != d`
// De Morgan's law, only when every comparison in the `&&`/`||` chain
@@ -1059,9 +1059,10 @@ fn test_remove_dead_expr_nullish_related() {
fn test_mangle_boolean_with_side_effects() {
let falsy_no_side_effects = ["!1", "\"\"", "0", "0n", "null", "void 0"];
for value in falsy_no_side_effects {
let negated = if value == "!1" { "0" } else { value };
test(&format!("y(x && {value})"), &format!("y(x && {value});"));
test(&format!("y(x || {value})"), &format!("y(x || {value});"));
test(&format!("y(!(x && {value}))"), &format!("y(!(x && {value}));"));
test(&format!("y(!(x && {value}))"), &format!("y(!(x && {negated}));"));
test(&format!("y(!(x || {value}))"), "y(!x);");
test(&format!("if (x && {value}) y"), "x;");
test(&format!("if (x || {value}) y"), "x && y;");
@@ -1076,10 +1077,11 @@ fn test_mangle_boolean_with_side_effects() {
let truthy_no_side_effects =
["!0", "\" \"", "1", "1n", "/./", "(() => {\n})", "function() {\n}", "[1, 2]", "{ a: 0 }"];
for value in truthy_no_side_effects {
let negated = if value == "!0" { "1" } else { value };
test(&format!("y(x && {value})"), &format!("y(x && {value});"));
test(&format!("y(x || {value})"), &format!("y(x || {value});"));
test(&format!("y(!(x && {value}))"), "y(!x);");
test(&format!("y(!(x || {value}))"), &format!("y(!(x || {value}));"));
test(&format!("y(!(x || {value}))"), &format!("y(!(x || {negated}));"));
test(&format!("if (x && {value}) y"), "x && y;");
test(&format!("if (x || {value}) y"), "x, y;");
test(&format!("if (x && {value}) y; else z"), "x ? y : z;");
@@ -197,54 +197,51 @@ fn test_while_continue_optimization() {
#[test]
fn test_do_continue_optimization() {
test("do{if(x)continue; x=3; continue; }while(true)", "do x||=3;while(!0);");
test("do{a();continue;b()}while(true)", "do a();while(!0)");
test("do{if(true){a();continue;}else;b();}while(true)", "do a();while(!0)");
test("do{if(false){a();continue;}else;b();continue;}while(true)", "do b();while(!0)");
test(
"do{if(a()){b();continue;}else;c();}while(true)",
"do{if(a()){b();continue}c()}while(!0);",
); // do a()?b():c();while(!0)
test("do{if(x)continue; x=3; continue; }while(true)", "do x||=3;while(1);");
test("do{a();continue;b()}while(true)", "do a();while(1)");
test("do{if(true){a();continue;}else;b();}while(true)", "do a();while(1)");
test("do{if(false){a();continue;}else;b();continue;}while(true)", "do b();while(1)");
test("do{if(a()){b();continue;}else;c();}while(true)", "do{if(a()){b();continue}c()}while(1);"); // do a()?b():c();while(1)
test(
"do{if(a()){b();}else{c();continue;}}while(true)",
"do if(a())b();else{c();continue}while(!0);",
); // do a()?b():c();while(!0)
test("do{if(a()){b();continue;}else;}while(true)", "do if(a()){b();continue}while(!0);"); // do a()&&b();while(!0)
test("do{if(a()){continue;}else{continue;} continue;}while(true)", "do a();while(!0)");
test("do{if(a()){continue;}else{continue;} b();}while(true)", "do a();while(!0)");
"do if(a())b();else{c();continue}while(1);",
); // do a()?b():c();while(1)
test("do{if(a()){b();continue;}else;}while(true)", "do if(a()){b();continue}while(1);"); // do a()&&b();while(1)
test("do{if(a()){continue;}else{continue;} continue;}while(true)", "do a();while(1)");
test("do{if(a()){continue;}else{continue;} b();}while(true)", "do a();while(1)");
test("do{while(a())continue;}while(true)", "do for(;a();)continue;while(!0);"); // do for(;a(););while(!0)
test("do{for(x in a())continue}while(true)", "do for(x in a())continue;while(!0);"); // do for(x in a());while(!0)
test("do{while(a())continue;}while(true)", "do for(;a();)continue;while(1);"); // do for(;a(););while(1)
test("do{for(x in a())continue}while(true)", "do for(x in a())continue;while(1);"); // do for(x in a());while(1)
test("do{while(a())break;}while(true)", "do for(;a();)break;while(!0)");
test("do for(x in a())break;while(true)", "do for(x in a())break;while(!0)");
test("do{while(a())break;}while(true)", "do for(;a();)break;while(1)");
test("do for(x in a())break;while(true)", "do for(x in a())break;while(1)");
test(
"do{try{continue;}catch(e){continue;}}while(true)",
"do try{continue}catch{continue}while(!0);",
); // do;while(!0)
"do try{continue}catch{continue}while(1);",
); // do;while(1)
test(
"do{try{if(a()){continue;}else{continue;} continue;}catch(e){}}while(true)",
"do try{if(a())continue;continue}catch{}while(!0);",
); // do try{a()}catch{}while(!0);
"do try{if(a())continue;continue}catch{}while(1);",
); // do try{a()}catch{}while(1);
test("do{g:continue}while(true)", "do g:continue;while(!0);"); // do;while(!0);
test("do{g:continue}while(true)", "do g:continue;while(1);"); // do;while(1);
// This case could be improved.
test(
"do{g:if(a()){continue;}else{continue;} continue;}while(true)",
"do g:if(a())continue;else continue;while(!0);",
); // do g:a();while(!0);
"do g:if(a())continue;else continue;while(1);",
); // do g:a();while(1);
test("do { foo(); continue; } while(false)", "do foo();while(!1)");
test("do { foo(); break; } while(false)", "do foo();while(!1)");
test("do { foo(); continue; } while(false)", "do foo();while(0)");
test("do { foo(); break; } while(false)", "do foo();while(0)");
test("do{break}while(fn());", "do break; while(fn());");
test("do{break}while(true);", "do break; while(!0);"); // do while(!1);
test("do{break}while(!new Date());", "do;while(!1);");
test("do{break}while(true);", "do break; while(1);"); // do while(0);
test("do{break}while(!new Date());", "do;while(0);");
test(
"do { foo(); switch (x) { case 1: break; default: f()} } while(false)",
"do switch (foo(),x) { case 1: break; default: f() } while(!1);",
"do switch (foo(),x) { case 1: break; default: f() } while(0);",
);
}
@@ -13,6 +13,11 @@ fn minimize_duplicate_nots() {
test_same("function k () { return !!x; }");
test("var k = () => { return !!x; }", "var k = () => !!x");
test_same("var k = () => !!x;");
// Negation of a `1`, `0` is boolean context.
test("var v = !(!0);", "var v = !1;");
test("var v = !(!1);", "var v = !0;");
test("var v = !(a || !0);", "var v = !(a || 1);");
test("var v = !(a && !1);", "var v = !(a && 0);");
}
#[test]
@@ -285,8 +285,8 @@ fn test_loop_optimization_edge_cases() {
test("while (true) { infiniteLoop(); }", "for (;;) infiniteLoop();"); // optimized loop form
// Test do-while loops - false becomes !1, braces may be removed, true becomes !0
test("do { executedOnce(); } while (false);", "do\n\texecutedOnce();\nwhile (!1);");
test("do { body(); } while (true);", "do\n\tbody();\nwhile (!0);");
test("do { executedOnce(); } while (false);", "do\n\texecutedOnce();\nwhile (0);");
test("do { body(); } while (true);", "do\n\tbody();\nwhile (1);");
// Test for loops with analyzable bounds
test(
@@ -38,7 +38,7 @@ fn test_fold_block() {
// test("for (x of y) {x}", "for(x of y);");
test("for (let x = 1; x <10; x++ ) {}", "for (let x = 1; x <10; x++ );");
test("for (var x = 1; x <10; x++ ) {}", "for (var x = 1; x <10; x++ );");
test("do { } while (true)", "do;while(!0)");
test("do { } while (true)", "do;while(1)");
test(
"function z(a) {
{
@@ -85,7 +85,7 @@ fn test_fold_useless_for() {
test("for (var se = [1, 2]; false;);", "var se = [1, 2];");
test("for (var se = [1, 2]; false;) { var a = 0; }", "var se = [1, 2], a;");
test("for (foo = bar; false;) {}", "for (foo = bar; !1;);");
test("for (foo = bar; false;) {}", "for (foo = bar; 0;);");
// test("l1:for(;false;) { }", "");
}
@@ -866,7 +866,7 @@ fn keep_script_root_var_in_nested_statement_after_cycle_removed() {
let source = "function outer() { function d1() { return x + d2() } function d2() { return d1() } return 1 } outer(); switch (1) { case 1: var x = 42; }";
// Script globals can be rebound through global-object properties without a
// resolved write reference, so the call cannot reuse a pure summary.
let expected = "function outer() { return 1 } if (outer(), !0) var x = 42;";
let expected = "function outer() { return 1 } if (outer(), 1) var x = 42;";
test_options_source_type(source, expected, SourceType::script(), &options);
// CommonJS top-level vars are wrapper-local, so ordinary counts may remove them.
@@ -16,7 +16,7 @@ App.tsx:
sys deallocs: 61
sys alloc bytes: 2128555 # 2.13 MB
sys peak growth: 1612427 # 1.61 MB
arena allocs: 15676
arena allocs: 15675
arena reallocs: 2721
arena size: 2880752 # 2.88 MB
@@ -49,9 +49,9 @@ antd.js:
sys deallocs: 1038
sys alloc bytes: 29976031 # 29.98 MB
sys peak growth: 19934725 # 19.93 MB
arena allocs: 351701
arena allocs: 351645
arena reallocs: 76248
arena size: 48772472 # 48.77 MB
arena size: 48770232 # 48.77 MB
binder.ts:
file size: 193077 # 193.08 kB
@@ -71,6 +71,6 @@ kitchen-sink.tsx:
sys deallocs: 1854
sys alloc bytes: 5307512 # 5.31 MB
sys peak growth: 3707425 # 3.71 MB
arena allocs: 64963
arena allocs: 64956
arena reallocs: 12252
arena size: 9393120 # 9.39 MB