diff --git a/crates/oxc_syntax/src/number.rs b/crates/oxc_syntax/src/number.rs index fa81f2a04..d2749641e 100644 --- a/crates/oxc_syntax/src/number.rs +++ b/crates/oxc_syntax/src/number.rs @@ -40,3 +40,87 @@ impl ToJsString for f64 { buffer.format(*self).to_string() } } + +/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm. +/// +/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32 +/// +/// This is copied from [Boa](https://github.com/boa-dev/boa/blob/61567687cf4bfeca6bd548c3e72b6965e74b2461/core/engine/src/builtins/number/conversions.rs) +pub trait ToJsInt32 { + fn to_js_int_32(&self) -> i32; +} + +impl ToJsInt32 for f64 { + #[allow(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + fn to_js_int_32(&self) -> i32 { + const SIGN_MASK: u64 = 0x8000_0000_0000_0000; + const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000; + const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; + const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000; + const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit. + const SIGNIFICAND_SIZE: i32 = 53; + + const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE; + const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1; + + fn is_denormal(number: f64) -> bool { + (number.to_bits() & EXPONENT_MASK) == 0 + } + + fn exponent(number: f64) -> i32 { + if is_denormal(number) { + return DENORMAL_EXPONENT; + } + + let d64 = number.to_bits(); + let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32; + + biased_e - EXPONENT_BIAS + } + + fn significand(number: f64) -> u64 { + let d64 = number.to_bits(); + let significand = d64 & SIGNIFICAND_MASK; + + if is_denormal(number) { + significand + } else { + significand + HIDDEN_BIT + } + } + + fn sign(number: f64) -> i64 { + if (number.to_bits() & SIGN_MASK) == 0 { + 1 + } else { + -1 + } + } + + let number = *self; + + if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { + let i = number as i32; + if f64::from(i) == number { + return i; + } + } + + let exponent = exponent(number); + let bits = if exponent < 0 { + if exponent <= -SIGNIFICAND_SIZE { + return 0; + } + + significand(number) >> -exponent + } else { + if exponent > 31 { + return 0; + } + + (significand(number) << exponent) & 0xFFFF_FFFF + }; + + (sign(number) * (bits as i64)) as i32 + } +} diff --git a/crates/oxc_transformer/src/typescript/conversions.rs b/crates/oxc_transformer/src/typescript/conversions.rs deleted file mode 100644 index 3113ead04..000000000 --- a/crates/oxc_transformer/src/typescript/conversions.rs +++ /dev/null @@ -1,83 +0,0 @@ -/// This file copied from [Boa](https://github.com/boa-dev/boa/blob/61567687cf4bfeca6bd548c3e72b6965e74b2461/core/engine/src/builtins/number/conversions.rs) - -/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm. -/// -/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32 -#[allow(clippy::float_cmp, clippy::cast_possible_truncation, clippy::cast_possible_wrap)] -pub(crate) fn f64_to_int32(number: f64) -> i32 { - const SIGN_MASK: u64 = 0x8000_0000_0000_0000; - const EXPONENT_MASK: u64 = 0x7FF0_0000_0000_0000; - const SIGNIFICAND_MASK: u64 = 0x000F_FFFF_FFFF_FFFF; - const HIDDEN_BIT: u64 = 0x0010_0000_0000_0000; - const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit. - const SIGNIFICAND_SIZE: i32 = 53; - - const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE; - const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1; - - fn is_denormal(number: f64) -> bool { - (number.to_bits() & EXPONENT_MASK) == 0 - } - - fn exponent(number: f64) -> i32 { - if is_denormal(number) { - return DENORMAL_EXPONENT; - } - - let d64 = number.to_bits(); - let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32; - - biased_e - EXPONENT_BIAS - } - - fn significand(number: f64) -> u64 { - let d64 = number.to_bits(); - let significand = d64 & SIGNIFICAND_MASK; - - if is_denormal(number) { - significand - } else { - significand + HIDDEN_BIT - } - } - - fn sign(number: f64) -> i64 { - if (number.to_bits() & SIGN_MASK) == 0 { - 1 - } else { - -1 - } - } - - if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { - let i = number as i32; - if f64::from(i) == number { - return i; - } - } - - let exponent = exponent(number); - let bits = if exponent < 0 { - if exponent <= -SIGNIFICAND_SIZE { - return 0; - } - - significand(number) >> -exponent - } else { - if exponent > 31 { - return 0; - } - - (significand(number) << exponent) & 0xFFFF_FFFF - }; - - (sign(number) * (bits as i64)) as i32 -} - -/// Converts a 64-bit floating point number to an `u32` according to the [`ToUint32`][ToUint32] algorithm. -/// -/// [ToUint32]: https://tc39.es/ecma262/#sec-touint32 -#[allow(clippy::cast_sign_loss)] -pub(crate) fn f64_to_uint32(number: f64) -> u32 { - f64_to_int32(number) as u32 -} diff --git a/crates/oxc_transformer/src/typescript/enum.rs b/crates/oxc_transformer/src/typescript/enum.rs index 15a329432..789e09dc7 100644 --- a/crates/oxc_transformer/src/typescript/enum.rs +++ b/crates/oxc_transformer/src/typescript/enum.rs @@ -4,15 +4,13 @@ use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, visit::walk_mut, VisitMut}; use oxc_span::{Atom, SPAN}; use oxc_syntax::{ - number::{NumberBase, ToJsString}, + number::{NumberBase, ToJsInt32, ToJsString}, operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator}, }; use rustc_hash::FxHashMap; use crate::context::Ctx; -use super::conversions::{f64_to_int32, f64_to_uint32}; - pub struct TypeScriptEnum<'a> { ctx: Ctx<'a>, enums: FxHashMap, FxHashMap, ConstantValue>>, @@ -376,7 +374,7 @@ impl<'a> TypeScriptEnum<'a> { } } - #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] + #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, clippy::cast_sign_loss)] fn eval_binary_expression( &self, expr: &BinaryExpression<'a>, @@ -414,22 +412,22 @@ impl<'a> TypeScriptEnum<'a> { match expr.operator { BinaryOperator::ShiftRight => Some(ConstantValue::Number(f64::from( - f64_to_int32(left).wrapping_shr(f64_to_uint32(right)), + left.to_js_int_32().wrapping_shr(right.to_js_int_32() as u32), ))), BinaryOperator::ShiftRightZeroFill => Some(ConstantValue::Number(f64::from( - f64_to_uint32(left).wrapping_shr(f64_to_uint32(right)), + (left.to_js_int_32() as u32).wrapping_shr(right.to_js_int_32() as u32), ))), BinaryOperator::ShiftLeft => Some(ConstantValue::Number(f64::from( - f64_to_int32(left).wrapping_shl(f64_to_uint32(right)), + left.to_js_int_32().wrapping_shl(right.to_js_int_32() as u32), ))), BinaryOperator::BitwiseXOR => { - Some(ConstantValue::Number(f64::from(f64_to_int32(left) ^ f64_to_int32(right)))) + Some(ConstantValue::Number(f64::from(left.to_js_int_32() ^ right.to_js_int_32()))) } BinaryOperator::BitwiseOR => { - Some(ConstantValue::Number(f64::from(f64_to_int32(left) | f64_to_int32(right)))) + Some(ConstantValue::Number(f64::from(left.to_js_int_32() | right.to_js_int_32()))) } BinaryOperator::BitwiseAnd => { - Some(ConstantValue::Number(f64::from(f64_to_int32(left) & f64_to_int32(right)))) + Some(ConstantValue::Number(f64::from(left.to_js_int_32() & right.to_js_int_32()))) } BinaryOperator::Multiplication => Some(ConstantValue::Number(left * right)), BinaryOperator::Division => Some(ConstantValue::Number(left / right)), @@ -467,7 +465,7 @@ impl<'a> TypeScriptEnum<'a> { UnaryOperator::UnaryPlus => Some(ConstantValue::Number(value)), UnaryOperator::UnaryNegation => Some(ConstantValue::Number(-value)), UnaryOperator::BitwiseNot => { - Some(ConstantValue::Number(f64::from(!f64_to_int32(value)))) + Some(ConstantValue::Number(f64::from(!value.to_js_int_32()))) } _ => None, } diff --git a/crates/oxc_transformer/src/typescript/mod.rs b/crates/oxc_transformer/src/typescript/mod.rs index 9203451c7..8fc91fa9c 100644 --- a/crates/oxc_transformer/src/typescript/mod.rs +++ b/crates/oxc_transformer/src/typescript/mod.rs @@ -1,6 +1,5 @@ mod annotations; mod collector; -mod conversions; mod diagnostics; mod r#enum; mod module;