boa/value/
equality.rs

1use super::*;
2use crate::{builtins::Number, Context};
3
4impl JsValue {
5    /// Strict equality comparison.
6    ///
7    /// This method is executed when doing strict equality comparisons with the `===` operator.
8    /// For more information, check <https://tc39.es/ecma262/#sec-strict-equality-comparison>.
9    pub fn strict_equals(&self, other: &Self) -> bool {
10        // 1. If Type(x) is different from Type(y), return false.
11        if self.get_type() != other.get_type() {
12            return false;
13        }
14
15        match (self, other) {
16            // 2. If Type(x) is Number or BigInt, then
17            //    a. Return ! Type(x)::equal(x, y).
18            (Self::BigInt(x), Self::BigInt(y)) => JsBigInt::equal(x, y),
19            (Self::Rational(x), Self::Rational(y)) => Number::equal(*x, *y),
20            (Self::Rational(x), Self::Integer(y)) => Number::equal(*x, f64::from(*y)),
21            (Self::Integer(x), Self::Rational(y)) => Number::equal(f64::from(*x), *y),
22            (Self::Integer(x), Self::Integer(y)) => x == y,
23
24            //Null has to be handled specially because "typeof null" returns object and if we managed
25            //this without a special case we would compare self and other as if they were actually
26            //objects which unfortunately fails
27            //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator
28            (Self::Null, Self::Null) => true,
29
30            // 3. Return ! SameValueNonNumeric(x, y).
31            (_, _) => Self::same_value_non_numeric(self, other),
32        }
33    }
34
35    /// Abstract equality comparison.
36    ///
37    /// This method is executed when doing abstract equality comparisons with the `==` operator.
38    ///  For more information, check <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
39    #[allow(clippy::float_cmp)]
40    pub fn equals(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
41        // 1. If Type(x) is the same as Type(y), then
42        //     a. Return the result of performing Strict Equality Comparison x === y.
43        if self.get_type() == other.get_type() {
44            return Ok(self.strict_equals(other));
45        }
46
47        Ok(match (self, other) {
48            // 2. If x is null and y is undefined, return true.
49            // 3. If x is undefined and y is null, return true.
50            (Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true,
51
52            // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y).
53            // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y.
54            //
55            // https://github.com/rust-lang/rust/issues/54883
56            (Self::Integer(_), Self::String(_))
57            | (Self::Rational(_), Self::String(_))
58            | (Self::String(_), Self::Integer(_))
59            | (Self::String(_), Self::Rational(_))
60            | (Self::Rational(_), Self::Boolean(_))
61            | (Self::Integer(_), Self::Boolean(_)) => {
62                let x = self.to_number(context)?;
63                let y = other.to_number(context)?;
64                Number::equal(x, y)
65            }
66
67            // 6. If Type(x) is BigInt and Type(y) is String, then
68            //    a. Let n be ! StringToBigInt(y).
69            //    b. If n is NaN, return false.
70            //    c. Return the result of the comparison x == n.
71            (Self::BigInt(ref a), Self::String(ref b)) => match JsBigInt::from_string(b) {
72                Some(ref b) => a == b,
73                None => false,
74            },
75
76            // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x.
77            (Self::String(ref a), Self::BigInt(ref b)) => match JsBigInt::from_string(a) {
78                Some(ref a) => a == b,
79                None => false,
80            },
81
82            // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.
83            (Self::Boolean(x), _) => return other.equals(&JsValue::new(*x as i32), context),
84
85            // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
86            (_, Self::Boolean(y)) => return self.equals(&JsValue::new(*y as i32), context),
87
88            // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result
89            // of the comparison x == ? ToPrimitive(y).
90            (Self::Object(_), _) => {
91                let primitive = self.to_primitive(context, PreferredType::Default)?;
92                return primitive.equals(other, context);
93            }
94
95            // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result
96            // of the comparison ? ToPrimitive(x) == y.
97            (_, Self::Object(_)) => {
98                let primitive = other.to_primitive(context, PreferredType::Default)?;
99                return primitive.equals(self, context);
100            }
101
102            // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then
103            //    a. If x or y are any of NaN, +∞, or -∞, return false.
104            //    b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false.
105            (Self::BigInt(ref a), Self::Rational(ref b)) => a == b,
106            (Self::Rational(ref a), Self::BigInt(ref b)) => a == b,
107            (Self::BigInt(ref a), Self::Integer(ref b)) => a == b,
108            (Self::Integer(ref a), Self::BigInt(ref b)) => a == b,
109
110            // 13. Return false.
111            _ => false,
112        })
113    }
114
115    /// The internal comparison abstract operation SameValue(x, y),
116    /// where x and y are ECMAScript language values, produces true or false.
117    ///
118    /// More information:
119    ///  - [ECMAScript][spec]
120    ///
121    /// [spec]: https://tc39.es/ecma262/#sec-samevalue
122    pub fn same_value(x: &JsValue, y: &JsValue) -> bool {
123        // 1. If Type(x) is different from Type(y), return false.
124        if x.get_type() != y.get_type() {
125            return false;
126        }
127
128        match (x, y) {
129            // 2. If Type(x) is Number or BigInt, then
130            //    a. Return ! Type(x)::SameValue(x, y).
131            (JsValue::BigInt(x), JsValue::BigInt(y)) => JsBigInt::same_value(x, y),
132            (JsValue::Rational(x), JsValue::Rational(y)) => Number::same_value(*x, *y),
133            (JsValue::Rational(x), JsValue::Integer(y)) => Number::same_value(*x, f64::from(*y)),
134            (JsValue::Integer(x), JsValue::Rational(y)) => Number::same_value(f64::from(*x), *y),
135            (JsValue::Integer(x), JsValue::Integer(y)) => x == y,
136
137            // 3. Return ! SameValueNonNumeric(x, y).
138            (_, _) => Self::same_value_non_numeric(x, y),
139        }
140    }
141
142    /// The internal comparison abstract operation `SameValueZero(x, y)`,
143    /// where `x` and `y` are ECMAScript language values, produces `true` or `false`.
144    ///
145    /// `SameValueZero` differs from SameValue only in its treatment of `+0` and `-0`.
146    ///
147    /// More information:
148    ///  - [ECMAScript][spec]
149    ///
150    /// [spec]: https://tc39.es/ecma262/#sec-samevaluezero
151    pub fn same_value_zero(x: &JsValue, y: &JsValue) -> bool {
152        if x.get_type() != y.get_type() {
153            return false;
154        }
155
156        match (x, y) {
157            // 2. If Type(x) is Number or BigInt, then
158            //    a. Return ! Type(x)::SameValueZero(x, y).
159            (JsValue::BigInt(x), JsValue::BigInt(y)) => JsBigInt::same_value_zero(x, y),
160
161            (JsValue::Rational(x), JsValue::Rational(y)) => Number::same_value_zero(*x, *y),
162            (JsValue::Rational(x), JsValue::Integer(y)) => {
163                Number::same_value_zero(*x, f64::from(*y))
164            }
165            (JsValue::Integer(x), JsValue::Rational(y)) => {
166                Number::same_value_zero(f64::from(*x), *y)
167            }
168            (JsValue::Integer(x), JsValue::Integer(y)) => x == y,
169
170            // 3. Return ! SameValueNonNumeric(x, y).
171            (_, _) => Self::same_value_non_numeric(x, y),
172        }
173    }
174
175    fn same_value_non_numeric(x: &JsValue, y: &JsValue) -> bool {
176        debug_assert!(x.get_type() == y.get_type());
177        match (x, y) {
178            (JsValue::Null, JsValue::Null) | (JsValue::Undefined, JsValue::Undefined) => true,
179            (JsValue::String(ref x), JsValue::String(ref y)) => x == y,
180            (JsValue::Boolean(x), JsValue::Boolean(y)) => x == y,
181            (JsValue::Object(ref x), JsValue::Object(ref y)) => JsObject::equals(x, y),
182            (JsValue::Symbol(ref x), JsValue::Symbol(ref y)) => x == y,
183            _ => false,
184        }
185    }
186}