feat: handle UnaryOpsWithBigInt (#475)

This commit is contained in:
阿良仔 2023-06-25 22:37:36 +08:00 committed by GitHub
parent b2e809812d
commit 26c3ece37c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 101 additions and 40 deletions

4
Cargo.lock generated
View file

@ -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",

View file

@ -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 }

View file

@ -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,

View file

@ -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)]

View file

@ -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 }

View file

@ -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)]

View file

@ -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 }
}

View file

@ -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 ``

View file

@ -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 }

View file

@ -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));
}
}
_ => {}
},
_ => {}
}
}

View file

@ -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');
}

View file

@ -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]

View file

@ -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}");
}

View file

@ -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")
}

View file

@ -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!"),