diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d34b606591..14409c44cf 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -16,6 +16,7 @@ use super::{ function::{make_builtin_fn, make_constructor_fn}, object::ObjectData, + value::AbstractRelation, }; use crate::{ builtins::value::{ResultValue, Value}, @@ -830,6 +831,7 @@ impl Number { /// x (a Number) and y (a Number). It performs the following steps when called: /// /// https://tc39.es/ecma262/#sec-numeric-types-number-equal + #[inline] #[allow(clippy::float_cmp)] pub(crate) fn equal(x: f64, y: f64) -> bool { x == y @@ -861,6 +863,7 @@ impl Number { /// x (a Number) and y (a Number). It performs the following steps when called: /// /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero + #[inline] #[allow(clippy::float_cmp)] pub(crate) fn same_value_zero(x: f64, y: f64) -> bool { if x.is_nan() && y.is_nan() { @@ -869,4 +872,28 @@ impl Number { x == y } + + #[inline] + #[allow(clippy::float_cmp)] + pub(crate) fn less_than(x: f64, y: f64) -> AbstractRelation { + if x.is_nan() || y.is_nan() { + return AbstractRelation::Undefined; + } + if x == y || x == 0.0 && y == -0.0 || x == -0.0 && y == 0.0 { + return AbstractRelation::False; + } + if x.is_infinite() && x.is_sign_positive() { + return AbstractRelation::False; + } + if y.is_infinite() && y.is_sign_positive() { + return AbstractRelation::True; + } + if x.is_infinite() && x.is_sign_negative() { + return AbstractRelation::False; + } + if y.is_infinite() && y.is_sign_negative() { + return AbstractRelation::True; + } + (x < y).into() + } } diff --git a/boa/src/builtins/property/mod.rs b/boa/src/builtins/property/mod.rs index 0cfaa49b96..0cc272bcda 100644 --- a/boa/src/builtins/property/mod.rs +++ b/boa/src/builtins/property/mod.rs @@ -14,8 +14,8 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty //! [section]: https://tc39.es/ecma262/#sec-property-attributes -use crate::builtins::value::rcstring::RcString; -use crate::builtins::value::rcsymbol::RcSymbol; +use crate::builtins::value::RcString; +use crate::builtins::value::RcSymbol; use crate::builtins::Value; use gc::{Finalize, Trace}; use std::fmt; diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 498b019bbc..b2016f3149 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -5,10 +5,6 @@ #[cfg(test)] mod tests; -pub mod val_type; - -pub use crate::builtins::value::val_type::Type; - use crate::builtins::{ function::Function, object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, @@ -28,20 +24,22 @@ use std::{ str::FromStr, }; -pub mod conversions; -pub mod display; -pub mod equality; -pub mod hash; -pub mod operations; -pub mod rcbigint; -pub mod rcstring; -pub mod rcsymbol; +mod conversions; +mod display; +mod equality; +mod hash; +mod operations; +mod rcbigint; +mod rcstring; +mod rcsymbol; +mod r#type; pub use conversions::*; pub(crate) use display::display_obj; pub use equality::*; pub use hash::*; pub use operations::*; +pub use r#type::Type; pub use rcbigint::RcBigInt; pub use rcstring::RcString; pub use rcsymbol::RcSymbol; diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index e4f562865e..c8ad4c39b5 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -1,5 +1,5 @@ use super::*; -use crate::builtins::number::{f64_to_int32, f64_to_uint32}; +use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; use crate::exec::PreferredType; impl Value { @@ -405,4 +405,211 @@ impl Value { pub fn not(&self, _: &mut Interpreter) -> ResultValue { Ok(Self::boolean(!self.to_boolean())) } + + /// Abstract relational comparison + /// + /// The comparison `x < y`, where `x` and `y` are values, produces `true`, `false`, + /// or `undefined` (which indicates that at least one operand is `NaN`). + /// + /// In addition to `x` and `y` the algorithm takes a Boolean flag named `LeftFirst` as a parameter. + /// The flag is used to control the order in which operations with potentially visible side-effects + /// are performed upon `x` and `y`. It is necessary because ECMAScript specifies left to right evaluation + /// of expressions. The default value of LeftFirst is `true` and indicates that the `x` parameter + /// corresponds to an expression that occurs to the left of the `y` parameter's corresponding expression. + /// + /// If `LeftFirst` is `false`, the reverse is the case and operations must be performed upon `y` before `x`. + /// + /// More Information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-abstract-relational-comparison + pub fn abstract_relation( + &self, + other: &Self, + left_first: bool, + ctx: &mut Interpreter, + ) -> Result { + Ok(match (self, other) { + // Fast path (for some common operations): + (Value::Integer(x), Value::Integer(y)) => (x < y).into(), + (Value::Integer(x), Value::Rational(y)) => Number::less_than(f64::from(*x), *y), + (Value::Rational(x), Value::Integer(y)) => Number::less_than(*x, f64::from(*y)), + (Value::Rational(x), Value::Rational(y)) => Number::less_than(*x, *y), + (Value::BigInt(ref x), Value::BigInt(ref y)) => (x < y).into(), + + // Slow path: + (_, _) => { + let (px, py) = if left_first { + let px = ctx.to_primitive(self, PreferredType::Number)?; + let py = ctx.to_primitive(other, PreferredType::Number)?; + (px, py) + } else { + // NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation. + let py = ctx.to_primitive(other, PreferredType::Number)?; + let px = ctx.to_primitive(self, PreferredType::Number)?; + (px, py) + }; + + match (px, py) { + (Value::String(ref x), Value::String(ref y)) => { + if x.starts_with(y.as_str()) { + return Ok(AbstractRelation::False); + } + if y.starts_with(x.as_str()) { + return Ok(AbstractRelation::True); + } + for (x, y) in x.chars().zip(y.chars()) { + if x != y { + return Ok((x < y).into()); + } + } + unreachable!() + } + (Value::BigInt(ref x), Value::String(ref y)) => { + if let Some(y) = string_to_bigint(&y) { + (*x.as_inner() < y).into() + } else { + AbstractRelation::Undefined + } + } + (Value::String(ref x), Value::BigInt(ref y)) => { + if let Some(x) = string_to_bigint(&x) { + (x < *y.as_inner()).into() + } else { + AbstractRelation::Undefined + } + } + (px, py) => match (ctx.to_numeric(&px)?, ctx.to_numeric(&py)?) { + (Value::Rational(x), Value::Rational(y)) => Number::less_than(x, y), + (Value::BigInt(ref x), Value::BigInt(ref y)) => (x < y).into(), + (Value::BigInt(ref x), Value::Rational(y)) => { + if y.is_nan() { + return Ok(AbstractRelation::Undefined); + } + if y.is_infinite() { + return Ok(y.is_sign_positive().into()); + } + let n = if y.is_sign_negative() { + y.floor() + } else { + y.ceil() + }; + (*x.as_inner() < BigInt::try_from(n).unwrap()).into() + } + (Value::Rational(x), Value::BigInt(ref y)) => { + if x.is_nan() { + return Ok(AbstractRelation::Undefined); + } + if x.is_infinite() { + return Ok(x.is_sign_negative().into()); + } + let n = if x.is_sign_negative() { + x.floor() + } else { + x.ceil() + }; + (BigInt::try_from(n).unwrap() < *y.as_inner()).into() + } + (_, _) => unreachable!("to_numeric should only retrun number and bigint"), + }, + } + } + }) + } + + /// The less than operator (`<`) returns `true` if the left operand is less than the right operand, + /// and `false` otherwise. + /// + /// More Information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than + /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation + #[inline] + pub fn lt(&self, other: &Self, ctx: &mut Interpreter) -> Result { + match self.abstract_relation(other, true, ctx)? { + AbstractRelation::True => Ok(true), + AbstractRelation::False | AbstractRelation::Undefined => Ok(false), + } + } + + /// The less than or equal operator (`<=`) returns `true` if the left operand is less than + /// or equal to the right operand, and `false` otherwise. + /// + /// More Information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than_or_equal + /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation + #[inline] + pub fn le(&self, other: &Self, ctx: &mut Interpreter) -> Result { + match other.abstract_relation(self, false, ctx)? { + AbstractRelation::False => Ok(true), + AbstractRelation::True | AbstractRelation::Undefined => Ok(false), + } + } + + /// The greater than operator (`>`) returns `true` if the left operand is greater than + /// the right operand, and `false` otherwise. + /// + /// More Information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than + /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation + #[inline] + pub fn gt(&self, other: &Self, ctx: &mut Interpreter) -> Result { + match other.abstract_relation(self, false, ctx)? { + AbstractRelation::True => Ok(true), + AbstractRelation::False | AbstractRelation::Undefined => Ok(false), + } + } + + /// The greater than or equal operator (`>=`) returns `true` if the left operand is greater than + /// or equal to the right operand, and `false` otherwise. + /// + /// More Information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than_or_equal + /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation + #[inline] + pub fn ge(&self, other: &Self, ctx: &mut Interpreter) -> Result { + match self.abstract_relation(other, true, ctx)? { + AbstractRelation::False => Ok(true), + AbstractRelation::True | AbstractRelation::Undefined => Ok(false), + } + } +} + +/// The result of the [Abstract Relational Comparison][arc]. +/// +/// Comparison `x < y`, where `x` and `y` are values. +/// It produces `true`, `false`, or `undefined` +/// (which indicates that at least one operand is `NaN`). +/// +/// [arc]: https://tc39.es/ecma262/#sec-abstract-relational-comparison +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AbstractRelation { + /// `x` is less than `y` + True, + /// `x` is **not** less than `y` + False, + /// Indicates that at least one operand is `NaN` + Undefined, +} + +impl From for AbstractRelation { + #[inline] + fn from(value: bool) -> Self { + if value { + AbstractRelation::True + } else { + AbstractRelation::False + } + } } diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index dbc6596233..6401b72cbe 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -502,3 +502,667 @@ toString: { }"# ); } + +mod abstract_relational_comparison { + use super::*; + macro_rules! check_comparison { + ($engine:ident, $string:expr => $expect:expr) => { + assert_eq!( + forward_val(&mut $engine, $string).unwrap().to_boolean(), + $expect + ); + }; + } + + #[test] + fn number_less_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 < 2" => true); + check_comparison!(engine, "2 < 2" => false); + check_comparison!(engine, "3 < 2" => false); + check_comparison!(engine, "2 < 2.5" => true); + check_comparison!(engine, "2.5 < 2" => false); + } + + #[test] + fn string_less_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1' < 2" => true); + check_comparison!(engine, "'2' < 2" => false); + check_comparison!(engine, "'3' < 2" => false); + check_comparison!(engine, "'2' < 2.5" => true); + check_comparison!(engine, "'2.5' < 2" => false); + } + + #[test] + fn number_less_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 < '2'" => true); + check_comparison!(engine, "2 < '2'" => false); + check_comparison!(engine, "3 < '2'" => false); + check_comparison!(engine, "2 < '2.5'" => true); + check_comparison!(engine, "2.5 < '2'" => false); + } + + #[test] + fn number_object_less_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) < '2'" => true); + check_comparison!(engine, "new Number(2) < '2'" => false); + check_comparison!(engine, "new Number(3) < '2'" => false); + check_comparison!(engine, "new Number(2) < '2.5'" => true); + check_comparison!(engine, "new Number(2.5) < '2'" => false); + } + + #[test] + fn number_object_less_than_number_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) < new Number(2)" => true); + check_comparison!(engine, "new Number(2) < new Number(2)" => false); + check_comparison!(engine, "new Number(3) < new Number(2)" => false); + check_comparison!(engine, "new Number(2) < new Number(2.5)" => true); + check_comparison!(engine, "new Number(2.5) < new Number(2)" => false); + } + + #[test] + fn string_less_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'hello' < 'hello'" => false); + check_comparison!(engine, "'hell' < 'hello'" => true); + check_comparison!(engine, "'hello, world' < 'world'" => true); + check_comparison!(engine, "'aa' < 'ab'" => true); + } + + #[test] + fn string_object_less_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') < 'hello'" => false); + check_comparison!(engine, "new String('hell') < 'hello'" => true); + check_comparison!(engine, "new String('hello, world') < 'world'" => true); + check_comparison!(engine, "new String('aa') < 'ab'" => true); + } + + #[test] + fn string_object_less_than_string_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') < new String('hello')" => false); + check_comparison!(engine, "new String('hell') < new String('hello')" => true); + check_comparison!(engine, "new String('hello, world') < new String('world')" => true); + check_comparison!(engine, "new String('aa') < new String('ab')" => true); + } + + #[test] + fn bigint_less_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1n < 10" => true); + check_comparison!(engine, "10n < 10" => false); + check_comparison!(engine, "100n < 10" => false); + check_comparison!(engine, "10n < 10.9" => true); + } + + #[test] + fn number_less_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "10 < 1n" => false); + check_comparison!(engine, "1 < 1n" => false); + check_comparison!(engine, "-1 < -1n" => false); + check_comparison!(engine, "-1.9 < -1n" => true); + } + + #[test] + fn negative_infnity_less_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "-Infinity < -10000000000n" => true); + check_comparison!(engine, "-Infinity < (-1n << 100n)" => true); + } + + #[test] + fn bigint_less_than_infinity() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n < NaN" => false); + check_comparison!(engine, "(1n << 100n) < NaN" => false); + } + + #[test] + fn nan_less_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "NaN < -10000000000n" => false); + check_comparison!(engine, "NaN < (-1n << 100n)" => false); + } + + #[test] + fn bigint_less_than_nan() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n < Infinity" => true); + check_comparison!(engine, "(1n << 100n) < Infinity" => true); + } + + #[test] + fn bigint_less_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n < '1000'" => false); + check_comparison!(engine, "1000n < '2000'" => true); + check_comparison!(engine, "1n < '-1'" => false); + check_comparison!(engine, "2n < '-1'" => false); + check_comparison!(engine, "-100n < 'InvalidBigInt'" => false); + } + + #[test] + fn string_less_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1000' < 1000n" => false); + check_comparison!(engine, "'2000' < 1000n" => false); + check_comparison!(engine, "'500' < 1000n" => true); + check_comparison!(engine, "'-1' < 1n" => true); + check_comparison!(engine, "'-1' < 2n" => true); + check_comparison!(engine, "'InvalidBigInt' < -100n" => false); + } + + // ------------------------------------------- + + #[test] + fn number_less_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 <= 2" => true); + check_comparison!(engine, "2 <= 2" => true); + check_comparison!(engine, "3 <= 2" => false); + check_comparison!(engine, "2 <= 2.5" => true); + check_comparison!(engine, "2.5 <= 2" => false); + } + + #[test] + fn string_less_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1' <= 2" => true); + check_comparison!(engine, "'2' <= 2" => true); + check_comparison!(engine, "'3' <= 2" => false); + check_comparison!(engine, "'2' <= 2.5" => true); + check_comparison!(engine, "'2.5' < 2" => false); + } + + #[test] + fn number_less_than_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 <= '2'" => true); + check_comparison!(engine, "2 <= '2'" => true); + check_comparison!(engine, "3 <= '2'" => false); + check_comparison!(engine, "2 <= '2.5'" => true); + check_comparison!(engine, "2.5 <= '2'" => false); + } + + #[test] + fn number_object_less_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) <= '2'" => true); + check_comparison!(engine, "new Number(2) <= '2'" => true); + check_comparison!(engine, "new Number(3) <= '2'" => false); + check_comparison!(engine, "new Number(2) <= '2.5'" => true); + check_comparison!(engine, "new Number(2.5) <= '2'" => false); + } + + #[test] + fn number_object_less_than_number_or_equal_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) <= new Number(2)" => true); + check_comparison!(engine, "new Number(2) <= new Number(2)" => true); + check_comparison!(engine, "new Number(3) <= new Number(2)" => false); + check_comparison!(engine, "new Number(2) <= new Number(2.5)" => true); + check_comparison!(engine, "new Number(2.5) <= new Number(2)" => false); + } + + #[test] + fn string_less_than_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'hello' <= 'hello'" => true); + check_comparison!(engine, "'hell' <= 'hello'" => true); + check_comparison!(engine, "'hello, world' <= 'world'" => true); + check_comparison!(engine, "'aa' <= 'ab'" => true); + } + + #[test] + fn string_object_less_than_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') <= 'hello'" => true); + check_comparison!(engine, "new String('hell') <= 'hello'" => true); + check_comparison!(engine, "new String('hello, world') <= 'world'" => true); + check_comparison!(engine, "new String('aa') <= 'ab'" => true); + } + + #[test] + fn string_object_less_than_string_or_equal_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') <= new String('hello')" => true); + check_comparison!(engine, "new String('hell') <= new String('hello')" => true); + check_comparison!(engine, "new String('hello, world') <= new String('world')" => true); + check_comparison!(engine, "new String('aa') <= new String('ab')" => true); + } + + #[test] + fn bigint_less_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1n <= 10" => true); + check_comparison!(engine, "10n <= 10" => true); + check_comparison!(engine, "100n <= 10" => false); + check_comparison!(engine, "10n <= 10.9" => true); + } + + #[test] + fn number_less_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "10 <= 1n" => false); + check_comparison!(engine, "1 <= 1n" => true); + check_comparison!(engine, "-1 <= -1n" => true); + check_comparison!(engine, "-1.9 <= -1n" => true); + } + + #[test] + fn negative_infnity_less_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "-Infinity <= -10000000000n" => true); + check_comparison!(engine, "-Infinity <= (-1n << 100n)" => true); + } + + #[test] + fn bigint_less_than_or_equal_infinity() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n <= NaN" => false); + check_comparison!(engine, "(1n << 100n) <= NaN" => false); + } + + #[test] + fn nan_less_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "NaN <= -10000000000n" => false); + check_comparison!(engine, "NaN <= (-1n << 100n)" => false); + } + + #[test] + fn bigint_less_than_or_equal_nan() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n <= Infinity" => true); + check_comparison!(engine, "(1n << 100n) <= Infinity" => true); + } + + #[test] + fn bigint_less_than_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n <= '1000'" => true); + check_comparison!(engine, "1000n <= '2000'" => true); + check_comparison!(engine, "1n <= '-1'" => false); + check_comparison!(engine, "2n <= '-1'" => false); + check_comparison!(engine, "-100n <= 'InvalidBigInt'" => false); + } + + #[test] + fn string_less_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1000' <= 1000n" => true); + check_comparison!(engine, "'2000' <= 1000n" => false); + check_comparison!(engine, "'500' <= 1000n" => true); + check_comparison!(engine, "'-1' <= 1n" => true); + check_comparison!(engine, "'-1' <= 2n" => true); + check_comparison!(engine, "'InvalidBigInt' <= -100n" => false); + } + + // ------------------------------------------- + + #[test] + fn number_greater_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 > 2" => false); + check_comparison!(engine, "2 > 2" => false); + check_comparison!(engine, "3 > 2" => true); + check_comparison!(engine, "2 > 2.5" => false); + check_comparison!(engine, "2.5 > 2" => true); + } + + #[test] + fn string_greater_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1' > 2" => false); + check_comparison!(engine, "'2' > 2" => false); + check_comparison!(engine, "'3' > 2" => true); + check_comparison!(engine, "'2' > 2.5" => false); + check_comparison!(engine, "'2.5' > 2" => true); + } + + #[test] + fn number_less_greater_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 > '2'" => false); + check_comparison!(engine, "2 > '2'" => false); + check_comparison!(engine, "3 > '2'" => true); + check_comparison!(engine, "2 > '2.5'" => false); + check_comparison!(engine, "2.5 > '2'" => true); + } + + #[test] + fn number_object_greater_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) > '2'" => false); + check_comparison!(engine, "new Number(2) > '2'" => false); + check_comparison!(engine, "new Number(3) > '2'" => true); + check_comparison!(engine, "new Number(2) > '2.5'" => false); + check_comparison!(engine, "new Number(2.5) > '2'" => true); + } + + #[test] + fn number_object_greater_than_number_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) > new Number(2)" => false); + check_comparison!(engine, "new Number(2) > new Number(2)" => false); + check_comparison!(engine, "new Number(3) > new Number(2)" => true); + check_comparison!(engine, "new Number(2) > new Number(2.5)" => false); + check_comparison!(engine, "new Number(2.5) > new Number(2)" => true); + } + + #[test] + fn string_greater_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'hello' > 'hello'" => false); + check_comparison!(engine, "'hell' > 'hello'" => false); + check_comparison!(engine, "'hello, world' > 'world'" => false); + check_comparison!(engine, "'aa' > 'ab'" => false); + check_comparison!(engine, "'ab' > 'aa'" => true); + } + + #[test] + fn string_object_greater_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') > 'hello'" => false); + check_comparison!(engine, "new String('hell') > 'hello'" => false); + check_comparison!(engine, "new String('hello, world') > 'world'" => false); + check_comparison!(engine, "new String('aa') > 'ab'" => false); + check_comparison!(engine, "new String('ab') > 'aa'" => true); + } + + #[test] + fn string_object_greater_than_string_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') > new String('hello')" => false); + check_comparison!(engine, "new String('hell') > new String('hello')" => false); + check_comparison!(engine, "new String('hello, world') > new String('world')" => false); + check_comparison!(engine, "new String('aa') > new String('ab')" => false); + check_comparison!(engine, "new String('ab') > new String('aa')" => true); + } + + #[test] + fn bigint_greater_than_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1n > 10" => false); + check_comparison!(engine, "10n > 10" => false); + check_comparison!(engine, "100n > 10" => true); + check_comparison!(engine, "10n > 10.9" => false); + } + + #[test] + fn number_greater_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "10 > 1n" => true); + check_comparison!(engine, "1 > 1n" => false); + check_comparison!(engine, "-1 > -1n" => false); + check_comparison!(engine, "-1.9 > -1n" => false); + } + + #[test] + fn negative_infnity_greater_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "-Infinity > -10000000000n" => false); + check_comparison!(engine, "-Infinity > (-1n << 100n)" => false); + } + + #[test] + fn bigint_greater_than_infinity() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n > NaN" => false); + check_comparison!(engine, "(1n << 100n) > NaN" => false); + } + + #[test] + fn nan_greater_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "NaN > -10000000000n" => false); + check_comparison!(engine, "NaN > (-1n << 100n)" => false); + } + + #[test] + fn bigint_greater_than_nan() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n > Infinity" => false); + check_comparison!(engine, "(1n << 100n) > Infinity" => false); + } + + #[test] + fn bigint_greater_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n > '1000'" => false); + check_comparison!(engine, "1000n > '2000'" => false); + check_comparison!(engine, "1n > '-1'" => true); + check_comparison!(engine, "2n > '-1'" => true); + check_comparison!(engine, "-100n > 'InvalidBigInt'" => false); + } + + #[test] + fn string_greater_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1000' > 1000n" => false); + check_comparison!(engine, "'2000' > 1000n" => true); + check_comparison!(engine, "'500' > 1000n" => false); + check_comparison!(engine, "'-1' > 1n" => false); + check_comparison!(engine, "'-1' > 2n" => false); + check_comparison!(engine, "'InvalidBigInt' > -100n" => false); + } + + // ---------------------------------------------- + + #[test] + fn number_greater_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 >= 2" => false); + check_comparison!(engine, "2 >= 2" => true); + check_comparison!(engine, "3 >= 2" => true); + check_comparison!(engine, "2 >= 2.5" => false); + check_comparison!(engine, "2.5 >= 2" => true); + } + + #[test] + fn string_greater_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1' >= 2" => false); + check_comparison!(engine, "'2' >= 2" => true); + check_comparison!(engine, "'3' >= 2" => true); + check_comparison!(engine, "'2' >= 2.5" => false); + check_comparison!(engine, "'2.5' >= 2" => true); + } + + #[test] + fn number_less_greater_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1 >= '2'" => false); + check_comparison!(engine, "2 >= '2'" => true); + check_comparison!(engine, "3 >= '2'" => true); + check_comparison!(engine, "2 >= '2.5'" => false); + check_comparison!(engine, "2.5 >= '2'" => true); + } + + #[test] + fn number_object_greater_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) >= '2'" => false); + check_comparison!(engine, "new Number(2) >= '2'" => true); + check_comparison!(engine, "new Number(3) >= '2'" => true); + check_comparison!(engine, "new Number(2) >= '2.5'" => false); + check_comparison!(engine, "new Number(2.5) >= '2'" => true); + } + + #[test] + fn number_object_greater_than_or_equal_number_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new Number(1) >= new Number(2)" => false); + check_comparison!(engine, "new Number(2) >= new Number(2)" => true); + check_comparison!(engine, "new Number(3) >= new Number(2)" => true); + check_comparison!(engine, "new Number(2) >= new Number(2.5)" => false); + check_comparison!(engine, "new Number(2.5) >= new Number(2)" => true); + } + + #[test] + fn string_greater_than_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'hello' >= 'hello'" => true); + check_comparison!(engine, "'hell' >= 'hello'" => false); + check_comparison!(engine, "'hello, world' >= 'world'" => false); + check_comparison!(engine, "'aa' >= 'ab'" => false); + check_comparison!(engine, "'ab' >= 'aa'" => true); + } + + #[test] + fn string_object_greater_or_equal_than_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') >= 'hello'" => true); + check_comparison!(engine, "new String('hell') >= 'hello'" => false); + check_comparison!(engine, "new String('hello, world') >= 'world'" => false); + check_comparison!(engine, "new String('aa') >= 'ab'" => false); + check_comparison!(engine, "new String('ab') >= 'aa'" => true); + } + + #[test] + fn string_object_greater_than_or_equal_string_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "new String('hello') >= new String('hello')" => true); + check_comparison!(engine, "new String('hell') >= new String('hello')" => false); + check_comparison!(engine, "new String('hello, world') >= new String('world')" => false); + check_comparison!(engine, "new String('aa') >= new String('ab')" => false); + check_comparison!(engine, "new String('ab') >= new String('aa')" => true); + } + + #[test] + fn bigint_greater_than_or_equal_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1n >= 10" => false); + check_comparison!(engine, "10n >= 10" => true); + check_comparison!(engine, "100n >= 10" => true); + check_comparison!(engine, "10n >= 10.9" => false); + } + + #[test] + fn number_greater_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "10 >= 1n" => true); + check_comparison!(engine, "1 >= 1n" => true); + check_comparison!(engine, "-1 >= -1n" => true); + check_comparison!(engine, "-1.9 >= -1n" => false); + } + + #[test] + fn negative_infnity_greater_or_equal_than_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "-Infinity >= -10000000000n" => false); + check_comparison!(engine, "-Infinity >= (-1n << 100n)" => false); + } + + #[test] + fn bigint_greater_than_or_equal_infinity() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n >= NaN" => false); + check_comparison!(engine, "(1n << 100n) >= NaN" => false); + } + + #[test] + fn nan_greater_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "NaN >= -10000000000n" => false); + check_comparison!(engine, "NaN >= (-1n << 100n)" => false); + } + + #[test] + fn bigint_greater_than_or_equal_nan() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n >= Infinity" => false); + check_comparison!(engine, "(1n << 100n) >= Infinity" => false); + } + + #[test] + fn bigint_greater_than_or_equal_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "1000n >= '1000'" => true); + check_comparison!(engine, "1000n >= '2000'" => false); + check_comparison!(engine, "1n >= '-1'" => true); + check_comparison!(engine, "2n >= '-1'" => true); + check_comparison!(engine, "-100n >= 'InvalidBigInt'" => false); + } + + #[test] + fn string_greater_than_or_equal_bigint() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + check_comparison!(engine, "'1000' >= 1000n" => true); + check_comparison!(engine, "'2000' >= 1000n" => true); + check_comparison!(engine, "'500' >= 1000n" => false); + check_comparison!(engine, "'-1' >= 1n" => false); + check_comparison!(engine, "'-1' >= 2n" => false); + check_comparison!(engine, "'InvalidBigInt' >= -100n" => false); + } +} diff --git a/boa/src/builtins/value/val_type.rs b/boa/src/builtins/value/type.rs similarity index 80% rename from boa/src/builtins/value/val_type.rs rename to boa/src/builtins/value/type.rs index 2861abd338..6ebeac5752 100644 --- a/boa/src/builtins/value/val_type.rs +++ b/boa/src/builtins/value/type.rs @@ -1,9 +1,8 @@ -use crate::builtins::value::Value; -use std::ops::Deref; +use super::Value; -/// Possible types of val as defined at https://tc39.es/ecma262/#sec-typeof-operator. +/// Possible types of values as defined at https://tc39.es/ecma262/#sec-typeof-operator. /// Note that an object which implements call is referred to here as 'Function'. -#[derive(Eq, PartialEq, Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Type { Undefined, Null, @@ -14,11 +13,10 @@ pub enum Type { BigInt, Object, Function, - Date, } impl Type { - pub fn as_str(&self) -> &str { + pub fn as_str(self) -> &'static str { match self { Self::Number => "number", Self::String => "string", @@ -29,7 +27,6 @@ impl Type { Self::Function => "function", Self::Object => "object", Self::BigInt => "bigint", - Self::Date => "date", } } } @@ -48,14 +45,14 @@ impl Value { Self::Symbol(_) => Type::Symbol, Self::Null => Type::Null, Self::Undefined => Type::Undefined, - Self::Object(ref o) => { - if o.deref().borrow().is_callable() { + Self::BigInt(_) => Type::BigInt, + Self::Object(ref object) => { + if object.borrow().is_function() { Type::Function } else { Type::Object } } - Self::BigInt(_) => Type::BigInt, } } } diff --git a/boa/src/exec/operator/mod.rs b/boa/src/exec/operator/mod.rs index cbbfd7617f..0b5a8c9671 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/exec/operator/mod.rs @@ -77,26 +77,26 @@ impl Executable for BinOp { } } op::BinOp::Comp(op) => { - let v_a = self.lhs().run(interpreter)?; - let v_b = self.rhs().run(interpreter)?; + let x = self.lhs().run(interpreter)?; + let y = self.rhs().run(interpreter)?; Ok(Value::from(match op { - CompOp::Equal => v_a.equals(&v_b, interpreter)?, - CompOp::NotEqual => !v_a.equals(&v_b, interpreter)?, - CompOp::StrictEqual => v_a.strict_equals(&v_b), - CompOp::StrictNotEqual => !v_a.strict_equals(&v_b), - CompOp::GreaterThan => v_a.to_number() > v_b.to_number(), - CompOp::GreaterThanOrEqual => v_a.to_number() >= v_b.to_number(), - CompOp::LessThan => v_a.to_number() < v_b.to_number(), - CompOp::LessThanOrEqual => v_a.to_number() <= v_b.to_number(), + CompOp::Equal => x.equals(&y, interpreter)?, + CompOp::NotEqual => !x.equals(&y, interpreter)?, + CompOp::StrictEqual => x.strict_equals(&y), + CompOp::StrictNotEqual => !x.strict_equals(&y), + CompOp::GreaterThan => x.gt(&y, interpreter)?, + CompOp::GreaterThanOrEqual => x.ge(&y, interpreter)?, + CompOp::LessThan => x.lt(&y, interpreter)?, + CompOp::LessThanOrEqual => x.le(&y, interpreter)?, CompOp::In => { - if !v_b.is_object() { + if !y.is_object() { return interpreter.throw_type_error(format!( "right-hand side of 'in' should be an object, got {}", - v_b.get_type().as_str() + y.get_type().as_str() )); } - let key = interpreter.to_property_key(&v_a)?; - interpreter.has_property(&v_b, &key) + let key = interpreter.to_property_key(&x)?; + interpreter.has_property(&y, &key) } })) }