Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ecmascript): add ToBigInt and StringToBigInt #6508

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/oxc_ecmascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ test = true
doctest = false

[dependencies]
num-traits = { workspace = true }
oxc_ast = { workspace = true }
oxc_span = { workspace = true }
oxc_syntax = { workspace = true }

num-bigint = { workspace = true }
num-traits = { workspace = true }
4 changes: 4 additions & 0 deletions crates/oxc_ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ mod prop_name;
mod string_char_at;
mod string_index_of;
mod string_last_index_of;
mod string_to_big_int;
mod to_big_int;
mod to_boolean;
mod to_int_32;
mod to_number;
Expand All @@ -23,6 +25,8 @@ pub use self::{
string_char_at::StringCharAt,
string_index_of::StringIndexOf,
string_last_index_of::StringLastIndexOf,
string_to_big_int::StringToBigInt,
to_big_int::ToBigInt,
to_boolean::ToBoolean,
to_int_32::ToInt32,
to_number::{NumberValue, ToNumber},
Expand Down
42 changes: 42 additions & 0 deletions crates/oxc_ecmascript/src/string_to_big_int.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use num_traits::Zero;

use num_bigint::BigInt;

/// `StringToBigInt`
///
/// <https://tc39.es/ecma262/#sec-stringtobigint>
pub trait StringToBigInt<'a> {
fn string_to_big_int(&self) -> Option<BigInt>;
}

impl<'a> StringToBigInt<'a> for &str {
fn string_to_big_int(&self) -> Option<BigInt> {
if self.contains('\u{000b}') {
// vertical tab is not always whitespace
return None;
}

let s = self.trim();

if s.is_empty() {
return Some(BigInt::zero());
}

if s.len() > 2 && s.starts_with('0') {
let radix: u32 = match s.chars().nth(1) {
Some('x' | 'X') => 16,
Some('o' | 'O') => 8,
Some('b' | 'B') => 2,
_ => 0,
};

if radix == 0 {
return None;
}

return BigInt::parse_bytes(s[2..].as_bytes(), radix);
}

return BigInt::parse_bytes(s.as_bytes(), 10);
}
}
70 changes: 70 additions & 0 deletions crates/oxc_ecmascript/src/to_big_int.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use num_bigint::BigInt;
use num_traits::{One, Zero};

use oxc_ast::ast::Expression;
use oxc_syntax::operator::UnaryOperator;

use crate::{StringToBigInt, ToBoolean, ToJsString};

/// `ToBigInt`
///
/// <https://tc39.es/ecma262/#sec-tobigint>
pub trait ToBigInt<'a> {
fn to_big_int(&self) -> Option<BigInt>;
}

impl<'a> ToBigInt<'a> for Expression<'a> {
#[expect(clippy::cast_possible_truncation)]
fn to_big_int(&self) -> Option<BigInt> {
match self {
Expression::NumericLiteral(number_literal) => {
let value = number_literal.value;
if value.abs() < 2_f64.powi(53) && value.fract() == 0.0 {
Some(BigInt::from(value as i64))
} else {
None
}
}
Expression::BigIntLiteral(bigint_literal) => {
let value = bigint_literal.raw.as_str().trim_end_matches('n').string_to_big_int();
debug_assert!(value.is_some(), "Failed to parse {}", bigint_literal.raw);
value
}
Expression::BooleanLiteral(bool_literal) => {
if bool_literal.value {
Some(BigInt::one())
} else {
Some(BigInt::zero())
}
}
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::LogicalNot => {
self.to_boolean().map(
|boolean| {
if boolean {
BigInt::one()
} else {
BigInt::zero()
}
},
)
}
UnaryOperator::UnaryNegation => {
unary_expr.argument.to_big_int().map(std::ops::Neg::neg)
}
UnaryOperator::BitwiseNot => {
unary_expr.argument.to_big_int().map(std::ops::Not::not)
}
UnaryOperator::UnaryPlus => unary_expr.argument.to_big_int(),
_ => None,
},
Expression::StringLiteral(string_literal) => {
string_literal.value.as_str().string_to_big_int()
}
Expression::TemplateLiteral(_) => {
self.to_js_string().and_then(|value| value.as_ref().string_to_big_int())
}
_ => None,
}
}
}
81 changes: 3 additions & 78 deletions crates/oxc_minifier/src/node_util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ mod may_have_side_effects;
use std::borrow::Cow;

use num_bigint::BigInt;
use num_traits::{One, Zero};
use oxc_ast::ast::*;
use oxc_ecmascript::{NumberValue, ToBoolean, ToJsString, ToNumber};
use oxc_ecmascript::{NumberValue, StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber};
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
use oxc_syntax::operator::UnaryOperator;

pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects};

Expand Down Expand Up @@ -114,55 +112,8 @@ pub trait NodeUtil<'a> {
expr.to_number()
}

#[allow(clippy::cast_possible_truncation)]
fn get_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
match expr {
Expression::NumericLiteral(number_literal) => {
let value = number_literal.value;
if value.abs() < 2_f64.powi(53) && is_exact_int64(value) {
Some(BigInt::from(value as i64))
} else {
None
}
}
Expression::BigIntLiteral(bigint_literal) => {
let value =
self.get_string_bigint_value(bigint_literal.raw.as_str().trim_end_matches('n'));
debug_assert!(value.is_some(), "Failed to parse {}", bigint_literal.raw);
value
}
Expression::BooleanLiteral(bool_literal) => {
if bool_literal.value {
Some(BigInt::one())
} else {
Some(BigInt::zero())
}
}
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::LogicalNot => self.get_boolean_value(expr).map(|boolean| {
if boolean.is_true() {
BigInt::one()
} else {
BigInt::zero()
}
}),
UnaryOperator::UnaryNegation => {
self.get_bigint_value(&unary_expr.argument).map(std::ops::Neg::neg)
}
UnaryOperator::BitwiseNot => {
self.get_bigint_value(&unary_expr.argument).map(std::ops::Not::not)
}
UnaryOperator::UnaryPlus => self.get_bigint_value(&unary_expr.argument),
_ => None,
},
Expression::StringLiteral(string_literal) => {
self.get_string_bigint_value(&string_literal.value)
}
Expression::TemplateLiteral(_) => {
self.get_string_value(expr).and_then(|value| self.get_string_bigint_value(&value))
}
_ => None,
}
expr.to_big_int()
}

/// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L234)
Expand All @@ -175,33 +126,7 @@ pub trait NodeUtil<'a> {

/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540)
fn get_string_bigint_value(&self, raw_string: &str) -> Option<BigInt> {
if raw_string.contains('\u{000b}') {
// vertical tab is not always whitespace
return None;
}

let s = raw_string.trim();

if s.is_empty() {
return Some(BigInt::zero());
}

if s.len() > 2 && s.starts_with('0') {
let radix: u32 = match s.chars().nth(1) {
Some('x' | 'X') => 16,
Some('o' | 'O') => 8,
Some('b' | 'B') => 2,
_ => 0,
};

if radix == 0 {
return None;
}

return BigInt::parse_bytes(s[2..].as_bytes(), radix);
}

return BigInt::parse_bytes(s.as_bytes(), 10);
raw_string.string_to_big_int()
}

/// Evaluate and attempt to determine which primitive value type it could resolve to.
Expand Down
15 changes: 0 additions & 15 deletions crates/oxc_minifier/src/tri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,6 @@ impl From<i8> for Tri {
}

impl Tri {
pub fn is_true(self) -> bool {
self == Tri::True
}

pub fn map<U, F>(self, f: F) -> Option<U>
where
F: FnOnce(Tri) -> U,
{
match self {
Self::True => Some(f(Tri::True)),
Self::False => Some(f(Tri::False)),
Self::Unknown => None,
}
}

pub fn not(self) -> Self {
match self {
Self::True => Self::False,
Expand Down