mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
refactor(ecmascript): remove NumberValue (#6519)
This commit is contained in:
parent
51fc63da45
commit
6d041fb469
5 changed files with 60 additions and 222 deletions
|
|
@ -18,17 +18,10 @@ mod to_number;
|
|||
mod to_string;
|
||||
|
||||
pub use self::{
|
||||
bound_names::BoundNames,
|
||||
is_simple_parameter_list::IsSimpleParameterList,
|
||||
private_bound_identifiers::PrivateBoundIdentifiers,
|
||||
prop_name::PropName,
|
||||
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},
|
||||
bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList,
|
||||
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
|
||||
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::ToNumber,
|
||||
to_string::ToJsString,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use num_traits::Zero;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::ast::*;
|
||||
|
||||
use crate::{NumberValue, ToNumber};
|
||||
use crate::ToNumber;
|
||||
|
||||
/// `ToBoolean`
|
||||
///
|
||||
|
|
@ -93,7 +94,7 @@ impl<'a> ToBoolean<'a> for Expression<'a> {
|
|||
// +1 -> true
|
||||
// +0 -> false
|
||||
// -0 -> false
|
||||
self.to_number().map(|value| value != NumberValue::Number(0_f64))
|
||||
self.to_number().map(|value| !value.is_zero())
|
||||
} else if unary_expr.operator == UnaryOperator::LogicalNot {
|
||||
// !true -> false
|
||||
unary_expr.argument.to_boolean().map(|b| !b)
|
||||
|
|
|
|||
|
|
@ -1,214 +1,59 @@
|
|||
use num_traits::Zero;
|
||||
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_syntax::operator::UnaryOperator;
|
||||
|
||||
use crate::ToBoolean;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum NumberValue {
|
||||
Number(f64),
|
||||
PositiveInfinity,
|
||||
NegativeInfinity,
|
||||
NaN,
|
||||
}
|
||||
|
||||
impl NumberValue {
|
||||
pub fn is_nan(&self) -> bool {
|
||||
matches!(self, Self::NaN)
|
||||
}
|
||||
}
|
||||
|
||||
impl Zero for NumberValue {
|
||||
fn zero() -> Self {
|
||||
Self::Number(0.0)
|
||||
}
|
||||
|
||||
fn is_zero(&self) -> bool {
|
||||
matches!(self, Self::Number(num) if num.is_zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Self> for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => match other {
|
||||
Self::Number(other_num) => Self::Number(num + other_num),
|
||||
Self::PositiveInfinity => Self::PositiveInfinity,
|
||||
Self::NegativeInfinity => Self::NegativeInfinity,
|
||||
Self::NaN => Self::NaN,
|
||||
},
|
||||
Self::NaN => Self::NaN,
|
||||
Self::PositiveInfinity => match other {
|
||||
Self::NaN | Self::NegativeInfinity => Self::NaN,
|
||||
_ => Self::PositiveInfinity,
|
||||
},
|
||||
Self::NegativeInfinity => match other {
|
||||
Self::NaN | Self::PositiveInfinity => Self::NaN,
|
||||
_ => Self::NegativeInfinity,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Self> for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
self + (-other)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Self> for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: Self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => match other {
|
||||
Self::Number(other_num) => Self::Number(num * other_num),
|
||||
Self::PositiveInfinity | Self::NegativeInfinity if num.is_zero() => Self::NaN,
|
||||
Self::PositiveInfinity => Self::PositiveInfinity,
|
||||
Self::NegativeInfinity => Self::NegativeInfinity,
|
||||
Self::NaN => Self::NaN,
|
||||
},
|
||||
Self::NaN => Self::NaN,
|
||||
Self::PositiveInfinity | Self::NegativeInfinity => match other {
|
||||
Self::Number(num) if num > 0.0 => self,
|
||||
Self::Number(num) if num < 0.0 => -self,
|
||||
Self::PositiveInfinity => self,
|
||||
Self::NegativeInfinity => -self,
|
||||
_ => Self::NaN,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<Self> for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: Self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => match other {
|
||||
Self::Number(other_num) if other_num.is_zero() => Self::NaN,
|
||||
Self::Number(other_num) => Self::Number(num / other_num),
|
||||
Self::PositiveInfinity | Self::NegativeInfinity if num < 0.0 => -other,
|
||||
Self::PositiveInfinity | Self::NegativeInfinity if num > 0.0 => other,
|
||||
_ => Self::NaN,
|
||||
},
|
||||
Self::NaN => Self::NaN,
|
||||
Self::PositiveInfinity | Self::NegativeInfinity => match other {
|
||||
Self::Number(num) if num > 0.0 => self,
|
||||
Self::Number(num) if num < 0.0 => -self,
|
||||
_ => Self::NaN,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Rem<Self> for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, other: Self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => match other {
|
||||
Self::Number(other_num) if other_num.is_zero() => Self::NaN,
|
||||
Self::Number(other_num) => Self::Number(num % other_num),
|
||||
Self::PositiveInfinity | Self::NegativeInfinity if num.is_zero() => Self::NaN,
|
||||
Self::PositiveInfinity | Self::NegativeInfinity => self,
|
||||
Self::NaN => Self::NaN,
|
||||
},
|
||||
Self::NaN => Self::NaN,
|
||||
Self::PositiveInfinity | Self::NegativeInfinity => match other {
|
||||
Self::Number(num) if !num.is_zero() => self,
|
||||
_ => Self::NaN,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
match self {
|
||||
Self::Number(num) => Self::Number(-num),
|
||||
Self::PositiveInfinity => Self::NegativeInfinity,
|
||||
Self::NegativeInfinity => Self::PositiveInfinity,
|
||||
Self::NaN => Self::NaN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for f64 {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
NumberValue::Number(num) => Ok(num),
|
||||
NumberValue::PositiveInfinity => Ok(Self::INFINITY),
|
||||
NumberValue::NegativeInfinity => Ok(Self::NEG_INFINITY),
|
||||
NumberValue::NaN => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `ToNumber`
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-tonumber>
|
||||
pub trait ToNumber<'a> {
|
||||
fn to_number(&self) -> Option<NumberValue>;
|
||||
fn to_number(&self) -> Option<f64>;
|
||||
}
|
||||
|
||||
impl<'a> ToNumber<'a> for Expression<'a> {
|
||||
fn to_number(&self) -> Option<NumberValue> {
|
||||
fn to_number(&self) -> Option<f64> {
|
||||
match self {
|
||||
Expression::NumericLiteral(number_literal) => {
|
||||
Some(NumberValue::Number(number_literal.value))
|
||||
}
|
||||
Expression::NumericLiteral(number_literal) => Some(number_literal.value),
|
||||
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||
UnaryOperator::UnaryPlus => unary_expr.argument.to_number(),
|
||||
UnaryOperator::UnaryNegation => unary_expr.argument.to_number().map(|v| -v),
|
||||
UnaryOperator::BitwiseNot => {
|
||||
unary_expr.argument.to_number().map(|value| {
|
||||
match value {
|
||||
NumberValue::Number(num) => NumberValue::Number(f64::from(
|
||||
!NumericLiteral::ecmascript_to_int32(num),
|
||||
)),
|
||||
// ~Infinity -> -1
|
||||
// ~-Infinity -> -1
|
||||
// ~NaN -> -1
|
||||
_ => NumberValue::Number(-1_f64),
|
||||
}
|
||||
})
|
||||
// UnaryOperator::BitwiseNot => {
|
||||
// unary_expr.argument.to_number().map(|value| {
|
||||
// match value {
|
||||
// NumberValue::Number(num) => NumberValue::Number(f64::from(
|
||||
// !NumericLiteral::ecmascript_to_int32(num),
|
||||
// )),
|
||||
// // ~Infinity -> -1
|
||||
// // ~-Infinity -> -1
|
||||
// // ~NaN -> -1
|
||||
// _ => NumberValue::Number(-1_f64),
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
UnaryOperator::LogicalNot => {
|
||||
self.to_boolean().map(|tri| if tri { 1_f64 } else { 0_f64 })
|
||||
}
|
||||
UnaryOperator::LogicalNot => self
|
||||
.to_boolean()
|
||||
.map(|tri| if tri { 1_f64 } else { 0_f64 })
|
||||
.map(NumberValue::Number),
|
||||
UnaryOperator::Void => Some(NumberValue::NaN),
|
||||
UnaryOperator::Void => Some(f64::NAN),
|
||||
_ => None,
|
||||
},
|
||||
Expression::BooleanLiteral(bool_literal) => {
|
||||
if bool_literal.value {
|
||||
Some(NumberValue::Number(1.0))
|
||||
Some(1.0)
|
||||
} else {
|
||||
Some(NumberValue::Number(0.0))
|
||||
Some(0.0)
|
||||
}
|
||||
}
|
||||
Expression::NullLiteral(_) => Some(NumberValue::Number(0.0)),
|
||||
Expression::NullLiteral(_) => Some(0.0),
|
||||
Expression::Identifier(ident) => match ident.name.as_str() {
|
||||
"Infinity" => Some(NumberValue::PositiveInfinity),
|
||||
"NaN" | "undefined" => Some(NumberValue::NaN),
|
||||
"Infinity" => Some(f64::INFINITY),
|
||||
"NaN" | "undefined" => Some(f64::NAN),
|
||||
_ => None,
|
||||
},
|
||||
// TODO: will be implemented in next PR, just for test pass now.
|
||||
Expression::StringLiteral(string_literal) => string_literal
|
||||
.value
|
||||
.parse::<f64>()
|
||||
.map_or(Some(NumberValue::NaN), |num| Some(NumberValue::Number(num))),
|
||||
Expression::StringLiteral(string_literal) => {
|
||||
string_literal.value.parse::<f64>().map_or(Some(f64::NAN), Some)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use num_bigint::BigInt;
|
|||
use num_traits::Zero;
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{NumberValue, ToInt32};
|
||||
use oxc_ecmascript::ToInt32;
|
||||
use oxc_span::{GetSpan, Span, SPAN};
|
||||
use oxc_syntax::{
|
||||
number::NumberBase,
|
||||
|
|
@ -492,8 +492,8 @@ impl<'a> PeepholeFoldConstants {
|
|||
if !operation.operator.is_arithmetic() {
|
||||
return None;
|
||||
};
|
||||
let left: f64 = ctx.get_number_value(&operation.left)?.try_into().ok()?;
|
||||
let right: f64 = ctx.get_number_value(&operation.right)?.try_into().ok()?;
|
||||
let left = ctx.get_number_value(&operation.left)?;
|
||||
let right = ctx.get_number_value(&operation.right)?;
|
||||
if !left.is_finite() || !right.is_finite() {
|
||||
return Self::try_fold_infinity_arithmetic(left, operation.operator, right, ctx);
|
||||
}
|
||||
|
|
@ -664,7 +664,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
if matches!((left, right), (Ty::Number, Ty::Str)) || matches!(right, Ty::Boolean) {
|
||||
let right_number = ctx.get_side_free_number_value(right_expr);
|
||||
|
||||
if let Some(NumberValue::Number(num)) = right_number {
|
||||
if let Some(num) = right_number {
|
||||
let number_literal_expr = ctx.ast.expression_numeric_literal(
|
||||
right_expr.span(),
|
||||
num,
|
||||
|
|
@ -685,7 +685,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
if matches!((left, right), (Ty::Str, Ty::Number)) || matches!(left, Ty::Boolean) {
|
||||
let left_number = ctx.get_side_free_number_value(left_expr);
|
||||
|
||||
if let Some(NumberValue::Number(num)) = left_number {
|
||||
if let Some(num) = left_number {
|
||||
let number_literal_expr = ctx.ast.expression_numeric_literal(
|
||||
left_expr.span(),
|
||||
num,
|
||||
|
|
@ -777,19 +777,19 @@ impl<'a> PeepholeFoldConstants {
|
|||
return Tri::from(l_big < r_big);
|
||||
}
|
||||
// try comparing as Numbers.
|
||||
(_, _, Some(l_num), Some(r_num)) => match (l_num, r_num) {
|
||||
(NumberValue::NaN, _) | (_, NumberValue::NaN) => {
|
||||
return Tri::from(will_negative);
|
||||
(_, _, Some(l_num), Some(r_num)) => {
|
||||
return if l_num.is_nan() || r_num.is_nan() {
|
||||
Tri::from(will_negative)
|
||||
} else {
|
||||
Tri::from(l_num < r_num)
|
||||
}
|
||||
(NumberValue::Number(l), NumberValue::Number(r)) => return Tri::from(l < r),
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
// Finally, try comparisons between BigInt and Number.
|
||||
(Some(l_big), _, _, Some(r_num)) => {
|
||||
return Self::bigint_less_than_number(&l_big, &r_num, Tri::False, will_negative);
|
||||
return Self::bigint_less_than_number(&l_big, r_num, Tri::False, will_negative);
|
||||
}
|
||||
(_, Some(r_big), Some(l_num), _) => {
|
||||
return Self::bigint_less_than_number(&r_big, &l_num, Tri::True, will_negative);
|
||||
return Self::bigint_less_than_number(&r_big, l_num, Tri::True, will_negative);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -801,29 +801,29 @@ impl<'a> PeepholeFoldConstants {
|
|||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn bigint_less_than_number(
|
||||
bigint_value: &BigInt,
|
||||
number_value: &NumberValue,
|
||||
number_value: f64,
|
||||
invert: Tri,
|
||||
will_negative: bool,
|
||||
) -> Tri {
|
||||
// if invert is false, then the number is on the right in tryAbstractRelationalComparison
|
||||
// if it's true, then the number is on the left
|
||||
match number_value {
|
||||
NumberValue::NaN => Tri::from(will_negative),
|
||||
NumberValue::PositiveInfinity => Tri::True.xor(invert),
|
||||
NumberValue::NegativeInfinity => Tri::False.xor(invert),
|
||||
NumberValue::Number(num) => {
|
||||
v if v.is_nan() => Tri::from(will_negative),
|
||||
v if v.is_infinite() && v.is_sign_positive() => Tri::True.xor(invert),
|
||||
v if v.is_infinite() && v.is_sign_negative() => Tri::False.xor(invert),
|
||||
num => {
|
||||
if let Some(Ordering::Equal | Ordering::Greater) =
|
||||
num.abs().partial_cmp(&2_f64.powi(53))
|
||||
{
|
||||
Tri::Unknown
|
||||
} else {
|
||||
let number_as_bigint = BigInt::from(*num as i64);
|
||||
let number_as_bigint = BigInt::from(num as i64);
|
||||
|
||||
match bigint_value.cmp(&number_as_bigint) {
|
||||
Ordering::Less => Tri::True.xor(invert),
|
||||
Ordering::Greater => Tri::False.xor(invert),
|
||||
Ordering::Equal => {
|
||||
if is_exact_int64(*num) {
|
||||
if is_exact_int64(num) {
|
||||
Tri::False
|
||||
} else {
|
||||
Tri::from(num.is_sign_positive()).xor(invert)
|
||||
|
|
@ -836,6 +836,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
||||
#[expect(clippy::float_cmp)]
|
||||
fn try_strict_equality_comparison<'b>(
|
||||
left_expr: &'b Expression<'a>,
|
||||
right_expr: &'b Expression<'a>,
|
||||
|
|
@ -920,9 +921,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
let left_num = ctx.get_side_free_number_value(left);
|
||||
let right_num = ctx.get_side_free_number_value(right);
|
||||
|
||||
if let (Some(NumberValue::Number(left_val)), Some(NumberValue::Number(right_val))) =
|
||||
(left_num, right_num)
|
||||
{
|
||||
if let (Some(left_val), Some(right_val)) = (left_num, right_num) {
|
||||
if left_val.fract() != 0.0 || right_val.fract() != 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::borrow::Cow;
|
|||
|
||||
use num_bigint::BigInt;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{NumberValue, StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber};
|
||||
use oxc_ecmascript::{StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber};
|
||||
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
||||
|
||||
pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects};
|
||||
|
|
@ -54,7 +54,7 @@ pub trait NodeUtil<'a> {
|
|||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L104-L114)
|
||||
/// Returns the number value of the node if it has one and it cannot have side effects.
|
||||
fn get_side_free_number_value(&self, expr: &Expression<'a>) -> Option<NumberValue> {
|
||||
fn get_side_free_number_value(&self, expr: &Expression<'a>) -> Option<f64> {
|
||||
let value = self.get_number_value(expr);
|
||||
// Calculating the number value, if any, is likely to be faster than calculating side effects,
|
||||
// and there are only a very few cases where we can compute a number value, but there could
|
||||
|
|
@ -108,7 +108,7 @@ pub trait NodeUtil<'a> {
|
|||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L348)
|
||||
/// Gets the value of a node as a Number, or None if it cannot be converted.
|
||||
/// This method does not consider whether `expr` may have side effects.
|
||||
fn get_number_value(&self, expr: &Expression<'a>) -> Option<NumberValue> {
|
||||
fn get_number_value(&self, expr: &Expression<'a>) -> Option<f64> {
|
||||
expr.to_number()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue