mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat: handle UnaryOpsWithBigInt (#475)
This commit is contained in:
parent
b2e809812d
commit
26c3ece37c
15 changed files with 101 additions and 40 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<bool> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<bool> {
|
||||
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)]
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<NumberValue> {
|
|||
/// 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<bool> {
|
||||
use num_traits::Zero;
|
||||
|
||||
match expr {
|
||||
Expression::RegExpLiteral(_)
|
||||
| Expression::ArrayExpression(_)
|
||||
|
|
@ -302,9 +303,7 @@ pub fn get_boolean_value(expr: &Expression) -> Option<bool> {
|
|||
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 ``
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
//!
|
||||
//! <https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! <https://github.com/google/closure-compiler/blob/master/test/com/google/javascript/jscomp/PeepholeFoldConstantsTest.java>
|
||||
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<BigUint, &'static str> {
|
||||
pub fn parse_big_int(s: &str, kind: Kind) -> Result<BigInt, &'static str> {
|
||||
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<BigUint, &'static str> {
|
|||
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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue