From cca0034e8b17c1ffac7836b86caf39bb4bf8763e Mon Sep 17 00:00:00 2001 From: 7086cmd <54303040+7086cmd@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:12:19 +0000 Subject: [PATCH] feat(minifier): handle positive `NaN` and `Infinity`. (#6207) `+NaN` -> `NaN`, `+Infinity` -> `Infinity`. --- .../src/ast_passes/peephole_fold_constants.rs | 92 +++++++++++++++---- tasks/minsize/minsize.snap | 10 +- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 728c3420a77d9..71cbb291ddb12 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1,14 +1,3 @@ -use std::cmp::Ordering; - -use num_bigint::BigInt; -use oxc_ast::ast::*; -use oxc_span::{GetSpan, Span, SPAN}; -use oxc_syntax::{ - number::NumberBase, - operator::{BinaryOperator, LogicalOperator, UnaryOperator}, -}; -use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; - use crate::{ node_util::{ is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, NumberValue, ValueType, @@ -17,6 +6,16 @@ use crate::{ ty::Ty, CompressorPass, }; +use num_bigint::BigInt; +use oxc_ast::ast::*; +use oxc_span::{GetSpan, Span, SPAN}; +use oxc_syntax::number::ToJsInt32; +use oxc_syntax::{ + number::NumberBase, + operator::{BinaryOperator, LogicalOperator, UnaryOperator}, +}; +use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; +use std::cmp::Ordering; /// Constant Folding /// @@ -139,6 +138,12 @@ impl<'a> PeepholeFoldConstants { expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { + fn is_within_i32_range(x: f64) -> bool { + x.is_finite() + && x.fract() == 0.0 + && x >= f64::from(i32::MIN) + && x <= f64::from(i32::MAX) + } match expr.operator { UnaryOperator::Void => self.try_reduce_void(expr, ctx), UnaryOperator::Typeof => self.try_fold_type_of(expr, ctx), @@ -162,9 +167,51 @@ impl<'a> PeepholeFoldConstants { matches!(unary.operator, UnaryOperator::UnaryNegation) .then(|| ctx.ast.move_expression(&mut expr.argument)) } + Expression::Identifier(id) if id.name == "Infinity" => { + Some(ctx.ast.move_expression(&mut expr.argument)) + } + // `+NaN` -> `NaN` + _ if expr.argument.is_nan() => Some(ctx.ast.move_expression(&mut expr.argument)), _ if expr.argument.is_number() => Some(ctx.ast.move_expression(&mut expr.argument)), _ => None, }, + UnaryOperator::BitwiseNot => match &mut expr.argument { + Expression::NumericLiteral(n) => is_within_i32_range(n.value).then(|| { + let value = !n.value.to_js_int_32(); + ctx.ast.expression_numeric_literal( + SPAN, + value.into(), + value.to_string(), + NumberBase::Decimal, + ) + }), + Expression::UnaryExpression(un) => { + match un.operator { + UnaryOperator::BitwiseNot => { + // Return the un-bitten value + Some(ctx.ast.move_expression(&mut un.argument)) + } + UnaryOperator::UnaryNegation if un.argument.is_number() => { + // `-~1` -> `2` + if let Expression::NumericLiteral(n) = &mut un.argument { + is_within_i32_range(n.value).then(|| { + let value = !(-n.value.to_js_int_32()); + ctx.ast.expression_numeric_literal( + SPAN, + value.into(), + value.to_string(), + NumberBase::Decimal, + ) + }) + } else { + None + } + } + _ => None, + } + } + _ => None, + }, _ => None, } } @@ -1268,9 +1315,9 @@ mod test { test_same("a=-Infinity"); test("a=-NaN", "a=NaN"); test_same("a=-foo()"); - // test("a=~~0", "a=0"); - // test("a=~~10", "a=10"); - // test("a=~-7", "a=6"); + test("a=~~0", "a=0"); + test("a=~~10", "a=10"); + test("a=~-7", "a=6"); // test("a=+true", "a=1"); test("a=+10", "a=10"); @@ -1279,8 +1326,8 @@ mod test { test_same("a=+f"); // test("a=+(f?true:false)", "a=+(f?1:0)"); test("a=+0", "a=0"); - // test("a=+Infinity", "a=Infinity"); - // test("a=+NaN", "a=NaN"); + test("a=+Infinity", "a=Infinity"); + test("a=+NaN", "a=NaN"); test("a=+-7", "a=-7"); // test("a=+.5", "a=.5"); @@ -1299,12 +1346,23 @@ mod test { } #[test] - #[ignore] fn test_unary_ops_string_compare() { test_same("a = -1"); test("a = ~0", "a = -1"); test("a = ~1", "a = -2"); test("a = ~101", "a = -102"); + + // More tests added by Ethan, which aligns with Google Closure Compiler's behavior + test_same("a = ~1.1"); // By default, we don't fold floating-point numbers. + test("a = ~0x3", "a = -4"); // Hexadecimal number + test("a = ~9", "a = -10"); // Despite `-10` is longer than `~9`, the compiler still folds it. + test_same("a = ~b"); + test_same("a = ~NaN"); + test_same("a = ~-Infinity"); + // TODO(7086cmd) We preserve it right now, since exceeded data's ~ calculation + // is hard to implement within one PR. + // test("x = ~2147483658.0", "x = 2147483647"); + // test("x = ~-2147483658", "x = -2147483649"); } #[test] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 858312f949565..1105bbf87d170 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -2,7 +2,7 @@ Original | Minified | esbuild | Gzip | esbuild 72.14 kB | 24.47 kB | 23.70 kB | 8.65 kB | 8.54 kB | react.development.js -173.90 kB | 61.71 kB | 59.82 kB | 19.56 kB | 19.33 kB | moment.js +173.90 kB | 61.70 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js 287.63 kB | 92.83 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js @@ -10,17 +10,17 @@ Original | Minified | esbuild | Gzip | esbuild 544.10 kB | 74.13 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js -555.77 kB | 278.71 kB | 270.13 kB | 91.40 kB | 90.80 kB | d3.js +555.77 kB | 278.70 kB | 270.13 kB | 91.39 kB | 90.80 kB | d3.js 1.01 MB | 470.11 kB | 458.89 kB | 126.97 kB | 126.71 kB | bundle.min.js -1.25 MB | 671.02 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js +1.25 MB | 671 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js -2.14 MB | 756.70 kB | 724.14 kB | 182.87 kB | 181.07 kB | victory.js +2.14 MB | 756.69 kB | 724.14 kB | 182.87 kB | 181.07 kB | victory.js 3.20 MB | 1.05 MB | 1.01 MB | 334.11 kB | 331.56 kB | echarts.js 6.69 MB | 2.44 MB | 2.31 MB | 498.90 kB | 488.28 kB | antd.js -10.95 MB | 3.59 MB | 3.49 MB | 913.91 kB | 915.50 kB | typescript.js +10.95 MB | 3.59 MB | 3.49 MB | 913.94 kB | 915.50 kB | typescript.js