diff --git a/crates/swc_ecma_minifier/src/compress/util/mod.rs b/crates/swc_ecma_minifier/src/compress/util/mod.rs index 5cc58bc72f5d..be656e3836a7 100644 --- a/crates/swc_ecma_minifier/src/compress/util/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/util/mod.rs @@ -4,7 +4,7 @@ use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; #[cfg(feature = "debug")] use swc_ecma_transforms_base::fixer::fixer; -use swc_ecma_utils::{ExprCtx, ExprExt, IdentUsageFinder, Value}; +use swc_ecma_utils::{number::JsNumber, ExprCtx, ExprExt, IdentUsageFinder, Value}; #[cfg(feature = "debug")] use swc_ecma_visit::{as_folder, FoldWith}; use swc_ecma_visit::{ @@ -512,16 +512,11 @@ pub(crate) fn eval_as_number(expr_ctx: &ExprCtx, e: &Expr) -> Option { if args.len() != 2 { return None; } - let base = eval_as_number(expr_ctx, &args[0].expr)?; - let exponent = eval_as_number(expr_ctx, &args[1].expr)?; + let base: JsNumber = eval_as_number(expr_ctx, &args[0].expr)?.into(); + let exponent: JsNumber = + eval_as_number(expr_ctx, &args[1].expr)?.into(); - // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate - // https://github.com/rust-lang/rust/issues/60468 - if exponent.is_nan() { - return Some(f64::NAN); - } - - return Some(base.powf(exponent)); + return Some(base.pow(exponent).into()); } _ => {} diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/input.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/input.js index 3aaaee6dea32..8b119d63a82e 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/input.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/input.js @@ -1 +1,2 @@ console.log(Math.pow(1, NaN)); +console.log(1 ** NaN); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.js index 89e3dd4ea485..5da7fad836f1 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.js @@ -1 +1,2 @@ console.log(NaN); +console.log(NaN); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.mangleOnly.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.mangleOnly.js index 3aaaee6dea32..8b119d63a82e 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.mangleOnly.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_nan/output.mangleOnly.js @@ -1 +1,2 @@ console.log(Math.pow(1, NaN)); +console.log(1 ** NaN); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/expected.stdout b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/expected.stdout index e50f2f14c9f3..0758f75c8939 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/expected.stdout +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/expected.stdout @@ -79,3 +79,85 @@ NaN Infinity -Infinity 1 +---- +NaN +NaN +NaN +NaN +1 +NaN +NaN +NaN +1 +NaN +Infinity +Infinity +Infinity +1 +Infinity +0 +0 +1 +NaN +Infinity +4 +2 +1 +1.4142135623730951 +0 +0.5 +1 +NaN +NaN +1 +1 +1 +1 +NaN +1 +1 +NaN +0 +0.25 +0.5 +1 +0.7071067811865476 +Infinity +2 +1 +NaN +0 +0 +0 +1 +0 +Infinity +Infinity +1 +NaN +Infinity +Infinity +-Infinity +1 +Infinity +0 +-0 +1 +NaN +NaN +1 +-1 +1 +NaN +NaN +-1 +1 +NaN +0 +0 +-0 +1 +0 +Infinity +-Infinity +1 diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/input.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/input.js index 562d5f9d153c..87f94f53b3dd 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/input.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/input.js @@ -79,3 +79,87 @@ console.log(Math.pow(-0, 0.5)); console.log(Math.pow(-0, -Infinity)); console.log(Math.pow(-0, -1)); console.log(Math.pow(-0, -0)); + +console.log("----"); + +console.log(NaN ** NaN); +console.log(NaN ** Infinity); +console.log(NaN ** 2); +console.log(NaN ** 1); +console.log(NaN ** 0); +console.log(NaN ** 0.5); +console.log(NaN ** -Infinity); +console.log(NaN ** -1); +console.log(NaN ** -0); +console.log(Infinity ** NaN); +console.log(Infinity ** Infinity); +console.log(Infinity ** 2); +console.log(Infinity ** 1); +console.log(Infinity ** 0); +console.log(Infinity ** 0.5); +console.log(Infinity ** -Infinity); +console.log(Infinity ** -1); +console.log(Infinity ** -0); +console.log(2 ** NaN); +console.log(2 ** Infinity); +console.log(2 ** 2); +console.log(2 ** 1); +console.log(2 ** 0); +console.log(2 ** 0.5); +console.log(2 ** -Infinity); +console.log(2 ** -1); +console.log(2 ** -0); +console.log(1 ** NaN); +console.log(1 ** Infinity); +console.log(1 ** 2); +console.log(1 ** 1); +console.log(1 ** 0); +console.log(1 ** 0.5); +console.log(1 ** -Infinity); +console.log(1 ** -1); +console.log(1 ** -0); +console.log(0.5 ** NaN); +console.log(0.5 ** Infinity); +console.log(0.5 ** 2); +console.log(0.5 ** 1); +console.log(0.5 ** 0); +console.log(0.5 ** 0.5); +console.log(0.5 ** -Infinity); +console.log(0.5 ** -1); +console.log(0.5 ** -0); +console.log(0 ** NaN); +console.log(0 ** Infinity); +console.log(0 ** 2); +console.log(0 ** 1); +console.log(0 ** 0); +console.log(0 ** 0.5); +console.log(0 ** -Infinity); +console.log(0 ** -1); +console.log(0 ** -0); +console.log((-Infinity) ** NaN); +console.log((-Infinity) ** Infinity); +console.log((-Infinity) ** 2); +console.log((-Infinity) ** 1); +console.log((-Infinity) ** 0); +console.log((-Infinity) ** 0.5); +console.log((-Infinity) ** -Infinity); +console.log((-Infinity) ** -1); +console.log((-Infinity) ** -0); +console.log((-1) ** NaN); +console.log((-1) ** Infinity); +console.log((-1) ** 2); +console.log((-1) ** 1); +console.log((-1) ** 0); +console.log((-1) ** 0.5); +console.log((-1) ** -Infinity); +console.log((-1) ** -1); +console.log((-1) ** -0); +console.log((-0) ** NaN); +console.log((-0) ** Infinity); +console.log((-0) ** 2); +console.log((-0) ** 1); +console.log((-0) ** 0); +console.log((-0) ** 0.5); +console.log((-0) ** -Infinity); +console.log((-0) ** -1); +console.log((-0) ** -0); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.js index 3d1bf52a0c95..d33b69748151 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.js @@ -79,3 +79,85 @@ console.log(0); console.log(Math.pow(-0, -1 / 0)); console.log(-Infinity); console.log(1); +console.log("----"); +console.log(NaN); +console.log(NaN); +console.log(NaN); +console.log(NaN); +console.log(1); +console.log(NaN); +console.log(NaN); +console.log(NaN); +console.log(1); +console.log(NaN); +console.log(Infinity); +console.log(Infinity); +console.log(Infinity); +console.log(1); +console.log(Infinity); +console.log(0); +console.log(0); +console.log(1); +console.log(NaN); +console.log(Infinity); +console.log(4); +console.log(2); +console.log(1); +console.log(1.4142135623730951); +console.log(0); +console.log(0.5); +console.log(1); +console.log(NaN); +console.log(NaN); +console.log(1); +console.log(1); +console.log(1); +console.log(1); +console.log(NaN); +console.log(1); +console.log(1); +console.log(NaN); +console.log(0); +console.log(0.25); +console.log(0.5); +console.log(1); +console.log(0.7071067811865476); +console.log(Infinity); +console.log(2); +console.log(1); +console.log(NaN); +console.log(0); +console.log(0); +console.log(0); +console.log(1); +console.log(0); +console.log(Infinity); +console.log(Infinity); +console.log(1); +console.log(NaN); +console.log(Infinity); +console.log(Infinity); +console.log(-Infinity); +console.log(1); +console.log(Infinity); +console.log(0); +console.log(-0); +console.log(1); +console.log(NaN); +console.log(NaN); +console.log(1); +console.log(-1); +console.log(1); +console.log(NaN); +console.log(NaN); +console.log(-1); +console.log(1); +console.log(NaN); +console.log(0); +console.log(0); +console.log(-0); +console.log(1); +console.log(0); +console.log(Infinity); +console.log(-Infinity); +console.log(1); diff --git a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.mangleOnly.js b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.mangleOnly.js index 562d5f9d153c..0cc18baa41ea 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.mangleOnly.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/evaluate/pow_spec/output.mangleOnly.js @@ -79,3 +79,85 @@ console.log(Math.pow(-0, 0.5)); console.log(Math.pow(-0, -Infinity)); console.log(Math.pow(-0, -1)); console.log(Math.pow(-0, -0)); +console.log("----"); +console.log(NaN ** NaN); +console.log(NaN ** Infinity); +console.log(NaN ** 2); +console.log(NaN ** 1); +console.log(NaN ** 0); +console.log(NaN ** 0.5); +console.log(NaN ** -Infinity); +console.log(NaN ** -1); +console.log(NaN ** -0); +console.log(Infinity ** NaN); +console.log(Infinity ** Infinity); +console.log(Infinity ** 2); +console.log(Infinity ** 1); +console.log(Infinity ** 0); +console.log(Infinity ** 0.5); +console.log(Infinity ** -Infinity); +console.log(Infinity ** -1); +console.log(Infinity ** -0); +console.log(2 ** NaN); +console.log(2 ** Infinity); +console.log(2 ** 2); +console.log(2 ** 1); +console.log(2 ** 0); +console.log(2 ** 0.5); +console.log(2 ** -Infinity); +console.log(2 ** -1); +console.log(2 ** -0); +console.log(1 ** NaN); +console.log(1 ** Infinity); +console.log(1 ** 2); +console.log(1 ** 1); +console.log(1 ** 0); +console.log(1 ** 0.5); +console.log(1 ** -Infinity); +console.log(1 ** -1); +console.log(1 ** -0); +console.log(0.5 ** NaN); +console.log(0.5 ** Infinity); +console.log(0.5 ** 2); +console.log(0.5 ** 1); +console.log(0.5 ** 0); +console.log(0.5 ** 0.5); +console.log(0.5 ** -Infinity); +console.log(0.5 ** -1); +console.log(0.5 ** -0); +console.log(0 ** NaN); +console.log(0 ** Infinity); +console.log(0 ** 2); +console.log(0 ** 1); +console.log(0 ** 0); +console.log(0 ** 0.5); +console.log(0 ** -Infinity); +console.log(0 ** -1); +console.log(0 ** -0); +console.log((-Infinity) ** NaN); +console.log((-Infinity) ** Infinity); +console.log((-Infinity) ** 2); +console.log((-Infinity) ** 1); +console.log((-Infinity) ** 0); +console.log((-Infinity) ** 0.5); +console.log((-Infinity) ** -Infinity); +console.log((-Infinity) ** -1); +console.log((-Infinity) ** -0); +console.log((-1) ** NaN); +console.log((-1) ** Infinity); +console.log((-1) ** 2); +console.log((-1) ** 1); +console.log((-1) ** 0); +console.log((-1) ** 0.5); +console.log((-1) ** -Infinity); +console.log((-1) ** -1); +console.log((-1) ** -0); +console.log((-0) ** NaN); +console.log((-0) ** Infinity); +console.log((-0) ** 2); +console.log((-0) ** 1); +console.log((-0) ** 0); +console.log((-0) ** 0.5); +console.log((-0) ** -Infinity); +console.log((-0) ** -1); +console.log((-0) ** -0); diff --git a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs index b20047df3828..f8d31f708331 100644 --- a/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs +++ b/crates/swc_ecma_transforms_optimization/src/simplify/expr/mod.rs @@ -13,8 +13,8 @@ use swc_ecma_transforms_base::{ perf::{cpu_count, Parallel, ParallelExt}, }; use swc_ecma_utils::{ - is_literal, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, NumberType, - ObjectType, StringType, SymbolType, UndefinedType, Value, + is_literal, number::JsNumber, prop_name_eq, to_int32, BoolType, ExprCtx, ExprExt, NullType, + NumberType, ObjectType, StringType, SymbolType, UndefinedType, Value, }; use swc_ecma_visit::{as_folder, standard_only_visit_mut, VisitMut, VisitMutWith}; use Value::{Known, Unknown}; @@ -1024,7 +1024,10 @@ impl SimplifyExpr { } if let (Known(lv), Known(rv)) = (lv, rv) { - return try_replace!(lv.powf(rv)); + let lv: JsNumber = lv.into(); + let rv: JsNumber = rv.into(); + let result: f64 = lv.pow(rv).into(); + return try_replace!(result); } return Unknown; diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index 5a67f30a24cc..bfe4ce4de3e3 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -621,14 +621,14 @@ impl Transform { enum_id: &Id, default_init: &TsEnumRecordValue, record: &TsEnumRecord, - top_level_mark: Mark, + unresolved_mark: Mark, ) -> TsEnumRecordValue { member .init .map(|expr| { EnumValueComputer { enum_id, - top_level_mark, + unresolved_mark, record, } .compute(expr) diff --git a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs index 1e26af7ec5c6..2026cf01c2d3 100644 --- a/crates/swc_ecma_transforms_typescript/src/ts_enum.rs +++ b/crates/swc_ecma_transforms_typescript/src/ts_enum.rs @@ -3,7 +3,10 @@ use std::mem; use swc_atoms::JsWord; use swc_common::{collections::AHashMap, Mark, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_utils::{number::ToJsString, ExprFactory}; +use swc_ecma_utils::{ + number::{JsNumber, ToJsString}, + ExprFactory, +}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -17,7 +20,7 @@ pub(crate) type TsEnumRecord = AHashMap; #[derive(Debug, Clone)] pub(crate) enum TsEnumRecordValue { String(JsWord), - Number(f64), + Number(JsNumber), Opaque(Box), Void, } @@ -25,7 +28,7 @@ pub(crate) enum TsEnumRecordValue { impl TsEnumRecordValue { pub fn inc(&self) -> Self { match self { - Self::Number(num) => Self::Number(num + 1.0), + Self::Number(num) => Self::Number((**num + 1.0).into()), _ => Self::Void, } } @@ -50,13 +53,13 @@ impl From for Expr { fn from(value: TsEnumRecordValue) -> Self { match value { TsEnumRecordValue::String(string) => Lit::Str(string.into()).into(), - TsEnumRecordValue::Number(num) if f64::is_nan(num) => Ident { + TsEnumRecordValue::Number(num) if num.is_nan() => Ident { span: DUMMY_SP, sym: "NaN".into(), ..Default::default() } .into(), - TsEnumRecordValue::Number(num) if f64::is_infinite(num) => { + TsEnumRecordValue::Number(num) if num.is_infinite() => { let value: Expr = Ident { span: DUMMY_SP, sym: "Infinity".into(), @@ -64,7 +67,7 @@ impl From for Expr { } .into(); - if f64::is_sign_negative(num) { + if num.is_sign_negative() { UnaryExpr { span: DUMMY_SP, op: op!(unary, "-"), @@ -77,7 +80,7 @@ impl From for Expr { } TsEnumRecordValue::Number(num) => Lit::Num(Number { span: DUMMY_SP, - value: num, + value: *num, raw: None, }) .into(), @@ -89,13 +92,13 @@ impl From for Expr { impl From for TsEnumRecordValue { fn from(value: f64) -> Self { - Self::Number(value) + Self::Number(value.into()) } } pub(crate) struct EnumValueComputer<'a> { pub enum_id: &'a Id, - pub top_level_mark: Mark, + pub unresolved_mark: Mark, pub record: &'a TsEnumRecord, } @@ -110,16 +113,16 @@ impl<'a> EnumValueComputer<'a> { fn compute_rec(&self, expr: Box) -> TsEnumRecordValue { match *expr { Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(s.value), - Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value), + Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()), Expr::Ident(Ident { ctxt, sym, .. }) - if &*sym == "NaN" && ctxt.has_mark(self.top_level_mark) => + if &*sym == "NaN" && ctxt.has_mark(self.unresolved_mark) => { - TsEnumRecordValue::Number(f64::NAN) + TsEnumRecordValue::Number(f64::NAN.into()) } Expr::Ident(Ident { ctxt, sym, .. }) - if &*sym == "Infinity" && ctxt.has_mark(self.top_level_mark) => + if &*sym == "Infinity" && ctxt.has_mark(self.unresolved_mark) => { - TsEnumRecordValue::Number(f64::INFINITY) + TsEnumRecordValue::Number(f64::INFINITY.into()) } Expr::Ident(ref ident) => self .record @@ -160,7 +163,7 @@ impl<'a> EnumValueComputer<'a> { match expr.op { op!(unary, "+") => TsEnumRecordValue::Number(num), op!(unary, "-") => TsEnumRecordValue::Number(-num), - op!("~") => TsEnumRecordValue::Number(!(num as i32) as f64), + op!("~") => TsEnumRecordValue::Number(!num), _ => unreachable!(), } } @@ -195,13 +198,13 @@ impl<'a> EnumValueComputer<'a> { op!("*") => left * right, op!("/") => left / right, op!("%") => left % right, - op!("**") => left.powf(right), - op!("<<") => (left.trunc() as i32).wrapping_shl(right.trunc() as u32) as f64, - op!(">>") => (left.trunc() as i32).wrapping_shr(right.trunc() as u32) as f64, - op!(">>>") => (left.trunc() as u32).wrapping_shr(right.trunc() as u32) as f64, - op!("|") => ((left.trunc() as i32) | (right.trunc() as i32)) as f64, - op!("&") => ((left.trunc() as i32) & (right.trunc() as i32)) as f64, - op!("^") => ((left.trunc() as i32) ^ (right.trunc() as i32)) as f64, + op!("**") => left.pow(right), + op!("<<") => left << right, + op!(">>") => left >> right, + op!(">>>") => left.unsigned_shr(right), + op!("|") => left | right, + op!("&") => left & right, + op!("^") => left ^ right, _ => unreachable!(), }; diff --git a/crates/swc_ecma_utils/src/number.rs b/crates/swc_ecma_utils/src/number.rs index 54d0ae892f38..12c9facb8600 100644 --- a/crates/swc_ecma_utils/src/number.rs +++ b/crates/swc_ecma_utils/src/number.rs @@ -9,3 +9,228 @@ impl ToJsString for f64 { buffer.format(*self).to_string() } } + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct JsNumber(f64); + +impl From for JsNumber { + fn from(v: f64) -> Self { + JsNumber(v) + } +} + +impl From for f64 { + fn from(v: JsNumber) -> Self { + v.0 + } +} + +impl std::ops::Deref for JsNumber { + type Target = f64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl JsNumber { + // https://tc39.es/ecma262/#sec-toint32 + fn as_int32(&self) -> i32 { + if !self.0.is_finite() { + return 0; + } + + self.0.trunc() as i32 + } + + // https://tc39.es/ecma262/#sec-touint32 + fn as_uint32(&self) -> u32 { + if !self.0.is_finite() { + return 0; + } + + self.0.trunc() as u32 + } +} + +// JsNumber + JsNumber +impl std::ops::Add for JsNumber { + type Output = JsNumber; + + fn add(self, rhs: JsNumber) -> Self::Output { + JsNumber(self.0 + rhs.0) + } +} + +// JsNumber - JsNumber +impl std::ops::Sub for JsNumber { + type Output = JsNumber; + + fn sub(self, rhs: JsNumber) -> Self::Output { + JsNumber(self.0 - rhs.0) + } +} + +// JsNumber * JsNumber +impl std::ops::Mul for JsNumber { + type Output = JsNumber; + + fn mul(self, rhs: JsNumber) -> Self::Output { + JsNumber(self.0 * rhs.0) + } +} + +// JsNumber / JsNumber +impl std::ops::Div for JsNumber { + type Output = JsNumber; + + fn div(self, rhs: JsNumber) -> Self::Output { + JsNumber(self.0 / rhs.0) + } +} + +// JsNumber % JsNumber +impl std::ops::Rem for JsNumber { + type Output = JsNumber; + + fn rem(self, rhs: JsNumber) -> Self::Output { + JsNumber(self.0 % rhs.0) + } +} + +// JsNumber ** JsNumber +impl JsNumber { + pub fn pow(self, rhs: JsNumber) -> JsNumber { + // https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate + // https://github.com/rust-lang/rust/issues/60468 + if rhs.0.is_nan() { + return JsNumber(f64::NAN); + } + + if self.0.abs() == 1f64 && rhs.0.is_infinite() { + return JsNumber(f64::NAN); + } + + JsNumber(self.0.powf(rhs.0)) + } +} + +// JsNumber << JsNumber +// https://tc39.es/ecma262/#sec-numeric-types-number-leftShift +impl std::ops::Shl for JsNumber { + type Output = JsNumber; + + fn shl(self, rhs: JsNumber) -> Self::Output { + JsNumber(self.as_int32().wrapping_shl(rhs.as_uint32()) as f64) + } +} + +// JsNumber >> JsNumber +// https://tc39.es/ecma262/#sec-numeric-types-number-signedRightShift +impl std::ops::Shr for JsNumber { + type Output = JsNumber; + + fn shr(self, rhs: JsNumber) -> Self::Output { + JsNumber((self.as_int32()).wrapping_shr(rhs.as_uint32()) as f64) + } +} + +// JsNumber >>> JsNumber +// https://tc39.es/ecma262/#sec-numeric-types-number-unsignedRightShift +impl JsNumber { + pub fn unsigned_shr(self, rhs: JsNumber) -> JsNumber { + JsNumber((self.as_uint32()).wrapping_shr(rhs.as_uint32()) as f64) + } +} + +// JsNumber | JsNumber +// https://tc39.es/ecma262/#sec-numberbitwiseop +impl std::ops::BitOr for JsNumber { + type Output = JsNumber; + + fn bitor(self, rhs: JsNumber) -> Self::Output { + JsNumber((self.as_int32() | rhs.as_int32()) as f64) + } +} + +// JsNumber & JsNumber +// https://tc39.es/ecma262/#sec-numberbitwiseop +impl std::ops::BitAnd for JsNumber { + type Output = JsNumber; + + fn bitand(self, rhs: JsNumber) -> Self::Output { + JsNumber((self.as_int32() & rhs.as_int32()) as f64) + } +} + +// JsNumber ^ JsNumber +// https://tc39.es/ecma262/#sec-numberbitwiseop +impl std::ops::BitXor for JsNumber { + type Output = JsNumber; + + fn bitxor(self, rhs: JsNumber) -> Self::Output { + JsNumber((self.as_int32() ^ rhs.as_int32()) as f64) + } +} + +// - JsNumber +impl std::ops::Neg for JsNumber { + type Output = JsNumber; + + fn neg(self) -> Self::Output { + JsNumber(-self.0) + } +} + +// ~ JsNumber +impl std::ops::Not for JsNumber { + type Output = JsNumber; + + fn not(self) -> Self::Output { + JsNumber(!(self.as_int32()) as f64) + } +} + +#[cfg(test)] +mod test_js_number { + use super::*; + + #[test] + fn test_as_int32() { + assert_eq!(JsNumber(f64::NAN).as_int32(), 0); + assert_eq!(JsNumber(0.0).as_int32(), 0); + assert_eq!(JsNumber(-0.0).as_int32(), 0); + assert_eq!(JsNumber(f64::INFINITY).as_int32(), 0); + assert_eq!(JsNumber(f64::NEG_INFINITY).as_int32(), 0); + } + + #[test] + fn test_as_uint32() { + assert_eq!(JsNumber(f64::NAN).as_uint32(), 0); + assert_eq!(JsNumber(0.0).as_uint32(), 0); + assert_eq!(JsNumber(-0.0).as_uint32(), 0); + assert_eq!(JsNumber(f64::INFINITY).as_uint32(), 0); + assert_eq!(JsNumber(f64::NEG_INFINITY).as_uint32(), 0); + } + + #[test] + fn test_add() { + assert_eq!(JsNumber(1.0) + JsNumber(2.0), JsNumber(3.0)); + + assert!((JsNumber(1.0) + JsNumber(f64::NAN)).is_nan()); + assert!((JsNumber(f64::NAN) + JsNumber(1.0)).is_nan()); + assert!((JsNumber(f64::NAN) + JsNumber(f64::NAN)).is_nan()); + assert!((JsNumber(f64::INFINITY) + JsNumber(f64::NEG_INFINITY)).is_nan()); + assert!((JsNumber(f64::NEG_INFINITY) + JsNumber(f64::INFINITY)).is_nan()); + + assert_eq!( + JsNumber(f64::INFINITY) + JsNumber(1.0), + JsNumber(f64::INFINITY) + ); + assert_eq!( + JsNumber(f64::NEG_INFINITY) + JsNumber(1.0), + JsNumber(f64::NEG_INFINITY) + ); + assert_eq!(JsNumber(-0.0) + JsNumber(0.0), JsNumber(-0.0)); + } +}