refactor(ecmascript): remove NumberValue (#6519)

This commit is contained in:
Boshen 2024-10-13 14:11:37 +00:00
parent 51fc63da45
commit 6d041fb469
5 changed files with 60 additions and 222 deletions

View file

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

View file

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

View file

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

View file

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

View file

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