diff --git a/Cargo.lock b/Cargo.lock index fca474344..c57b39dce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1190,6 +1190,7 @@ version = "0.0.6" dependencies = [ "bitflags 2.3.2", "num-bigint", + "num-traits", "oxc_allocator", "oxc_index", "oxc_span", @@ -1315,6 +1316,7 @@ version = "0.0.0" dependencies = [ "bitflags 2.3.2", "num-bigint", + "num-traits", "oxc_allocator", "oxc_index", "oxc_semantic", @@ -1374,6 +1376,8 @@ dependencies = [ "bitflags 2.3.2", "jemallocator", "mimalloc", + "num-bigint", + "num-traits", "oxc_allocator", "oxc_ast", "oxc_ast_lower", diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index ae62dff46..5b7b78f86 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -22,6 +22,7 @@ oxc_index = { workspace = true } bitflags = { workspace = true } rustc-hash = { workspace = true } num-bigint = { workspace = true } +num-traits = { workspace = true } serde = { workspace = true, features = ["derive"], optional = true } serde_json = { workspace = true, optional = true } diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 02ca51d30..b9999410c 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1,6 +1,5 @@ use std::fmt; -use num_bigint::BigUint; use oxc_allocator::{Box, Vec}; use oxc_span::{Atom, SourceType, Span}; use oxc_syntax::operator::{ @@ -210,11 +209,13 @@ impl<'a> Expression<'a> { /// Returns literal's value converted to the Boolean type /// returns `true` when node is truthy, `false` when node is falsy, `None` when it cannot be determined. pub fn get_boolean_value(&self) -> Option { + use num_traits::Zero; + match self { Self::BooleanLiteral(lit) => Some(lit.value), Self::NullLiteral(_) => Some(false), Self::NumberLiteral(lit) => Some(lit.value != 0.0), - Self::BigintLiteral(lit) => Some(lit.value != BigUint::new(vec![])), + Self::BigintLiteral(lit) => Some(!lit.value.is_zero()), Self::RegExpLiteral(_) => Some(true), Self::StringLiteral(lit) => Some(!lit.value.is_empty()), _ => None, diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index d71e89565..e1c8ff118 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -6,7 +6,7 @@ use std::{ }; use bitflags::bitflags; -use num_bigint::BigUint; +use num_bigint::BigInt; use oxc_span::{Atom, Span}; use oxc_syntax::NumberBase; #[cfg(feature = "serde")] @@ -70,7 +70,7 @@ pub struct BigintLiteral { #[cfg_attr(feature = "serde", serde(flatten))] pub span: Span, #[cfg_attr(feature = "serde", serde(serialize_with = "crate::serialize::serialize_bigint"))] - pub value: BigUint, + pub value: BigInt, } #[derive(Debug, Clone, Hash)] diff --git a/crates/oxc_hir/Cargo.toml b/crates/oxc_hir/Cargo.toml index d68c34c80..4ef9b0531 100644 --- a/crates/oxc_hir/Cargo.toml +++ b/crates/oxc_hir/Cargo.toml @@ -23,6 +23,7 @@ oxc_semantic = { workspace = true } bitflags = { workspace = true } num-bigint = { workspace = true } +num-traits = { workspace = true } serde = { workspace = true, features = ["derive"], optional = true } serde_json = { workspace = true, optional = true } diff --git a/crates/oxc_hir/src/hir.rs b/crates/oxc_hir/src/hir.rs index 8cebc650e..9f85a65c4 100644 --- a/crates/oxc_hir/src/hir.rs +++ b/crates/oxc_hir/src/hir.rs @@ -5,7 +5,7 @@ use std::{ }; use bitflags::bitflags; -use num_bigint::BigUint; +use num_bigint::BigInt; use oxc_allocator::{Box, Vec}; use oxc_semantic::{ReferenceFlag, ReferenceId, SymbolId}; use oxc_span::{Atom, Span}; @@ -182,11 +182,13 @@ impl<'a> Expression<'a> { /// Returns literal's value converted to the Boolean type /// returns `true` when node is truthy, `false` when node is falsy, `None` when it cannot be determined. pub fn get_boolean_value(&self) -> Option { + use num_traits::Zero; + match self { Self::BooleanLiteral(lit) => Some(lit.value), Self::NullLiteral(_) => Some(false), Self::NumberLiteral(lit) => Some(lit.value != 0.0), - Self::BigintLiteral(lit) => Some(lit.value != BigUint::new(vec![])), + Self::BigintLiteral(lit) => Some(!lit.value.is_zero()), Self::RegExpLiteral(_) => Some(true), Self::StringLiteral(lit) => Some(!lit.value.is_empty()), _ => None, @@ -287,7 +289,7 @@ pub struct BigintLiteral { #[cfg_attr(feature = "serde", serde(flatten))] pub span: Span, #[cfg_attr(feature = "serde", serde(serialize_with = "crate::serialize::serialize_bigint"))] - pub value: BigUint, + pub value: BigInt, } #[derive(Debug, Clone, Hash)] diff --git a/crates/oxc_hir/src/hir_builder.rs b/crates/oxc_hir/src/hir_builder.rs index a2ed0d4cb..8e2972c2e 100644 --- a/crates/oxc_hir/src/hir_builder.rs +++ b/crates/oxc_hir/src/hir_builder.rs @@ -5,7 +5,7 @@ clippy::unused_self, )] -use num_bigint::BigUint; +use num_bigint::BigInt; use oxc_allocator::{Allocator, Box, String, Vec}; use oxc_semantic::{ReferenceFlag, ReferenceId, SymbolId}; use oxc_span::{Atom, Span}; @@ -96,7 +96,7 @@ impl<'a> HirBuilder<'a> { NullLiteral { span } } - pub fn bigint_literal(&mut self, span: Span, value: BigUint) -> BigintLiteral { + pub fn bigint_literal(&mut self, span: Span, value: BigInt) -> BigintLiteral { BigintLiteral { span, value } } diff --git a/crates/oxc_hir/src/hir_util.rs b/crates/oxc_hir/src/hir_util.rs index dca51033f..46230fe5d 100644 --- a/crates/oxc_hir/src/hir_util.rs +++ b/crates/oxc_hir/src/hir_util.rs @@ -1,4 +1,3 @@ -use num_bigint::BigUint; use oxc_semantic::ReferenceFlag; use oxc_syntax::operator::{AssignmentOperator, LogicalOperator, UnaryOperator}; @@ -291,6 +290,8 @@ pub fn get_side_free_number_value(expr: &Expression) -> Option { /// such value can be determined by static analysis. /// This method does not consider whether the node may have side-effects. pub fn get_boolean_value(expr: &Expression) -> Option { + use num_traits::Zero; + match expr { Expression::RegExpLiteral(_) | Expression::ArrayExpression(_) @@ -302,9 +303,7 @@ pub fn get_boolean_value(expr: &Expression) -> Option { Expression::NullLiteral(_) => Some(false), Expression::BooleanLiteral(boolean_literal) => Some(boolean_literal.value), Expression::NumberLiteral(number_literal) => Some(number_literal.value != 0.0), - Expression::BigintLiteral(big_int_literal) => { - Some(big_int_literal.value == BigUint::default()) - } + Expression::BigintLiteral(big_int_literal) => Some(!big_int_literal.value.is_zero()), Expression::StringLiteral(string_literal) => Some(!string_literal.value.is_empty()), Expression::TemplateLiteral(template_literal) => { // only for `` diff --git a/crates/oxc_minifier/Cargo.toml b/crates/oxc_minifier/Cargo.toml index 9cea86130..575552612 100644 --- a/crates/oxc_minifier/Cargo.toml +++ b/crates/oxc_minifier/Cargo.toml @@ -21,6 +21,8 @@ oxc_semantic = { workspace = true } oxc_syntax = { workspace = true } oxc_index = { workspace = true } bitflags = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } [dev-dependencies] walkdir = { workspace = true } diff --git a/crates/oxc_minifier/src/compressor/fold.rs b/crates/oxc_minifier/src/compressor/fold.rs index a78f963b0..be1ad3487 100644 --- a/crates/oxc_minifier/src/compressor/fold.rs +++ b/crates/oxc_minifier/src/compressor/fold.rs @@ -2,6 +2,8 @@ //! //! +use std::ops::Not; + #[allow(clippy::wildcard_imports)] use oxc_hir::hir::*; use oxc_hir::hir_util::{ @@ -278,6 +280,7 @@ impl<'a> Compressor<'a> { None } + #[allow(clippy::too_many_lines)] fn try_fold_unary_operator<'b>( &mut self, unary_expr: &'b mut UnaryExpression<'a>, @@ -285,9 +288,10 @@ impl<'a> Compressor<'a> { if let Some(boolean) = get_boolean_value(&unary_expr.argument) { match unary_expr.operator { // !100 -> false + // !100n -> false // after this, it will be compressed to !1 or !0 in `compress_boolean` - UnaryOperator::LogicalNot => { - if let Expression::NumberLiteral(number_literal) = &unary_expr.argument { + UnaryOperator::LogicalNot => match &unary_expr.argument { + Expression::NumberLiteral(number_literal) => { let value = number_literal.value; // Don't fold !0 and !1 back to false. if value == 0_f64 || (value - 1_f64).abs() < f64::EPSILON { @@ -296,19 +300,24 @@ impl<'a> Compressor<'a> { let bool_literal = self.hir.boolean_literal(unary_expr.span, !boolean); return Some(self.hir.literal_boolean_expression(bool_literal)); } - } + Expression::BigintLiteral(_) => { + let bool_literal = self.hir.boolean_literal(unary_expr.span, !boolean); + return Some(self.hir.literal_boolean_expression(bool_literal)); + } + _ => {} + }, // +1 -> 1 // NaN -> NaN // +Infinity -> Infinity UnaryOperator::UnaryPlus => match &unary_expr.argument { Expression::NumberLiteral(number_literal) => { - let number_literal = self.hir.number_literal( + let literal = self.hir.number_literal( unary_expr.span, number_literal.value, number_literal.raw, number_literal.base, ); - return Some(self.hir.literal_number_expression(number_literal)); + return Some(self.hir.literal_number_expression(literal)); } Expression::Identifier(ident) => { if matches!(ident.name.as_str(), "NaN" | "Infinity") { @@ -322,7 +331,7 @@ impl<'a> Compressor<'a> { if let Some(value) = get_number_value(&unary_expr.argument) && let NumberValue::Number(value) = value { let raw = self.hir.new_str(value.to_string().as_str()); - let number_literal = self.hir.number_literal( + let literal = self.hir.number_literal( unary_expr.span, value, raw, @@ -332,7 +341,7 @@ impl<'a> Compressor<'a> { NumberBase::Float }, ); - return Some(self.hir.literal_number_expression(number_literal)); + return Some(self.hir.literal_number_expression(literal)); } } }, @@ -342,15 +351,21 @@ impl<'a> Compressor<'a> { Expression::NumberLiteral(number_literal) => { let value = -number_literal.value; let raw = self.hir.new_str(value.to_string().as_str()); - let number_literal = self.hir.number_literal( + let literal = self.hir.number_literal( unary_expr.span, value, raw, number_literal.base, ); - return Some(self.hir.literal_number_expression(number_literal)); + return Some(self.hir.literal_number_expression(literal)); + } + Expression::BigintLiteral(big_int_literal) => { + use std::ops::Neg; + + let value = big_int_literal.value.clone().neg(); + let literal = self.hir.bigint_literal(unary_expr.span, value); + return Some(self.hir.literal_bigint_expression(literal)); } - Expression::BigintLiteral(_big_int_literal) => return None, Expression::Identifier(ident) => { if ident.name == "NaN" { return self.try_detach_unary_op(unary_expr); @@ -359,18 +374,41 @@ impl<'a> Compressor<'a> { _ => {} }, // ~10 -> -11 - UnaryOperator::BitwiseNot => { - if let Expression::NumberLiteral(number_literal) = &unary_expr.argument && number_literal.value.fract() == 0.0 { - let int_value = NumberLiteral::ecmascript_to_int32(number_literal.value); - let number_literal = self.hir.number_literal( + // ~NaN -> -1 + UnaryOperator::BitwiseNot => match &unary_expr.argument { + Expression::NumberLiteral(number_literal) => { + if number_literal.value.fract() == 0.0 { + let int_value = + NumberLiteral::ecmascript_to_int32(number_literal.value); + let literal = self.hir.number_literal( unary_expr.span, f64::from(!int_value), number_literal.raw, NumberBase::Decimal, // since it be converted to i32, it should always be decimal. ); - return Some(self.hir.literal_number_expression(number_literal)) + return Some(self.hir.literal_number_expression(literal)); + } } - } + Expression::BigintLiteral(big_int_literal) => { + let value = big_int_literal.value.clone().not(); + let leteral = self.hir.bigint_literal(unary_expr.span, value); + return Some(self.hir.literal_bigint_expression(leteral)); + } + Expression::Identifier(ident) => { + if ident.name == "NaN" { + let value = -1_f64; + let raw = self.hir.new_str("-1"); + let literal = self.hir.number_literal( + unary_expr.span, + value, + raw, + NumberBase::Decimal, + ); + return Some(self.hir.literal_number_expression(literal)); + } + } + _ => {} + }, _ => {} } } diff --git a/crates/oxc_minifier/src/printer/gen.rs b/crates/oxc_minifier/src/printer/gen.rs index b75b800f6..a9107abd9 100644 --- a/crates/oxc_minifier/src/printer/gen.rs +++ b/crates/oxc_minifier/src/printer/gen.rs @@ -898,6 +898,11 @@ fn print_non_negative_float(value: f64, p: &mut Printer) -> String { impl Gen for BigintLiteral { fn gen(&self, p: &mut Printer, ctx: Context) { + use num_bigint::Sign; + + if self.value.sign() == Sign::Minus { + p.print_space_before_operator(Operator::Unary(UnaryOperator::UnaryNegation)); + } p.print_str(self.value.to_string().as_bytes()); p.print(b'n'); } diff --git a/crates/oxc_minifier/tests/closure/fold_constants.rs b/crates/oxc_minifier/tests/closure/fold_constants.rs index e7c3bf8a8..d201c58b5 100644 --- a/crates/oxc_minifier/tests/closure/fold_constants.rs +++ b/crates/oxc_minifier/tests/closure/fold_constants.rs @@ -1,6 +1,6 @@ //! -use crate::{test, test_same}; +use crate::{test, test_same, test_without_compress_booleans}; #[test] fn undefined_comparison1() { @@ -163,9 +163,9 @@ fn unary_ops() { #[test] fn unary_with_big_int() { test("-(1n)", "-1n"); - // test("- -1n", "1n"); - // test("!1n", "false"); - // test("~0n", "-1n"); + test("- -1n", "1n"); + test_without_compress_booleans("!1n", "false"); + test("~0n", "-1n"); } #[test] diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index 5764605d3..c59b0297d 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -7,7 +7,7 @@ mod oxc; mod tdewolff; mod terser; -use oxc_minifier::{Minifier, MinifierOptions}; +use oxc_minifier::{CompressOptions, Minifier, MinifierOptions, PrinterOptions}; use oxc_span::SourceType; pub(crate) fn test(source_text: &str, expected: &str) { @@ -28,3 +28,12 @@ pub(crate) fn test_reparse(source_text: &str) { let minified2 = Minifier::new(&minified, source_type, options).build(); assert_eq!(minified, minified2, "for source {source_text}"); } + +pub(crate) fn test_without_compress_booleans(source_text: &str, expected: &str) { + let source_type = SourceType::default(); + let compress_options = CompressOptions { booleans: false, ..CompressOptions::default() }; + let options = + MinifierOptions { mangle: false, compress: compress_options, print: PrinterOptions }; + let minified = Minifier::new(source_text, source_type, options).build(); + assert_eq!(expected, minified, "for source {source_text}"); +} diff --git a/crates/oxc_parser/src/lexer/number.rs b/crates/oxc_parser/src/lexer/number.rs index 84f04386d..94a3b3bf2 100644 --- a/crates/oxc_parser/src/lexer/number.rs +++ b/crates/oxc_parser/src/lexer/number.rs @@ -1,7 +1,7 @@ //! Parsing utilities for converting Javascript numbers to Rust f64 //! code copied from [jsparagus](https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/parser/src/numeric_value.rs) -use num_bigint::BigUint; +use num_bigint::BigInt; use super::kind::Kind; @@ -75,7 +75,7 @@ fn parse_hex(s: &str) -> f64 { result } -pub fn parse_big_int(s: &str, kind: Kind) -> Result { +pub fn parse_big_int(s: &str, kind: Kind) -> Result { let s = match kind { Kind::Decimal => s, Kind::Binary | Kind::Octal | Kind::Hex => &s[2..], @@ -88,5 +88,5 @@ pub fn parse_big_int(s: &str, kind: Kind) -> Result { Kind::Hex => 16, _ => unreachable!(), }; - BigUint::parse_bytes(s.as_bytes(), radix).ok_or("invalid bigint") + BigInt::parse_bytes(s.as_bytes(), radix).ok_or("invalid bigint") } diff --git a/crates/oxc_parser/src/lexer/token.rs b/crates/oxc_parser/src/lexer/token.rs index 4f8b5ccc7..230f57df2 100644 --- a/crates/oxc_parser/src/lexer/token.rs +++ b/crates/oxc_parser/src/lexer/token.rs @@ -1,6 +1,5 @@ //! Token -use num_bigint::BigUint; use oxc_ast::ast::RegExpFlags; use oxc_span::Span; @@ -43,7 +42,7 @@ impl<'a> Token<'a> { pub enum TokenValue<'a> { None, Number(f64), - BigInt(BigUint), + BigInt(num_bigint::BigInt), String(&'a str), RegExp(RegExp<'a>), } @@ -68,7 +67,7 @@ impl<'a> TokenValue<'a> { } } - pub fn as_bigint(&self) -> BigUint { + pub fn as_bigint(&self) -> num_bigint::BigInt { match self { Self::BigInt(s) => s.clone(), _ => panic!("expected bigint!"),