Skip to main content

boa_engine/value/
equality.rs

1use super::{JsBigInt, JsObject, JsResult, JsValue, PreferredType};
2#[cfg(feature = "annex-b")]
3use crate::builtins::is_html_dda::IsHTMLDDA;
4use crate::{Context, JsVariant, builtins::Number};
5use std::collections::HashSet;
6
7impl JsValue {
8    /// Inner loop of the deep equality comparison, strict.
9    pub(crate) fn deep_strict_equals_inner(
10        &self,
11        other: &Self,
12        encounters: &mut HashSet<usize>,
13        context: &mut Context,
14    ) -> JsResult<bool> {
15        match (self.as_object(), other.as_object()) {
16            (None, None) => Ok(self.strict_equals(other)),
17            (Some(x), Some(y)) => JsObject::deep_strict_equals_inner(&x, &y, encounters, context),
18            _ => Ok(false),
19        }
20    }
21
22    /// Deep strict equality.
23    ///
24    /// If the value is an object/array, also compare the key-values.
25    /// It uses `strict_equals()` for non-object values.
26    pub fn deep_strict_equals(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
27        self.deep_strict_equals_inner(other, &mut HashSet::new(), context)
28    }
29
30    /// Strict equality comparison.
31    ///
32    /// This method is executed when doing strict equality comparisons with the `===` operator.
33    /// For more information, check <https://tc39.es/ecma262/#sec-strict-equality-comparison>.
34    #[must_use]
35    pub fn strict_equals(&self, other: &Self) -> bool {
36        // 1. If Type(x) is different from Type(y), return false.
37        if self.get_type() != other.get_type() {
38            return false;
39        }
40
41        match (self.variant(), other.variant()) {
42            // 2. If Type(x) is Number or BigInt, then
43            //    a. Return ! Type(x)::equal(x, y).
44            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::equal(&x, &y),
45            (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::equal(x, y),
46            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::equal(x, f64::from(y)),
47            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::equal(f64::from(x), y),
48            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
49
50            //Null has to be handled specially because "typeof null" returns object and if we managed
51            //this without a special case we would compare self and other as if they were actually
52            //objects which unfortunately fails
53            //Specification Link: https://tc39.es/ecma262/#sec-typeof-operator
54            (JsVariant::Null, JsVariant::Null) => true,
55
56            // 3. Return ! SameValueNonNumeric(x, y).
57            (_, _) => Self::same_value_non_numeric(self, other),
58        }
59    }
60
61    /// Abstract equality comparison.
62    ///
63    /// This method is executed when doing abstract equality comparisons with the `==` operator.
64    ///  For more information, check <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
65    #[allow(clippy::float_cmp)]
66    pub fn equals(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
67        // 1. If Type(x) is the same as Type(y), then
68        //     a. Return the result of performing Strict Equality Comparison x === y.
69        if self.get_type() == other.get_type() {
70            return Ok(self.strict_equals(other));
71        }
72
73        Ok(match (self.variant(), other.variant()) {
74            // 2. If x is null and y is undefined, return true.
75            // 3. If x is undefined and y is null, return true.
76            (JsVariant::Null, JsVariant::Undefined) | (JsVariant::Undefined, JsVariant::Null) => {
77                true
78            }
79
80            // B.3.6.2: Objects with [[IsHTMLDDA]] are loosely equal to null and undefined.
81            #[cfg(feature = "annex-b")]
82            (JsVariant::Object(ref obj), JsVariant::Null | JsVariant::Undefined)
83                if obj.is::<IsHTMLDDA>() =>
84            {
85                true
86            }
87            #[cfg(feature = "annex-b")]
88            (JsVariant::Null | JsVariant::Undefined, JsVariant::Object(ref obj))
89                if obj.is::<IsHTMLDDA>() =>
90            {
91                true
92            }
93
94            // 3. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y).
95            // 4. If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y.
96            //
97            // https://github.com/rust-lang/rust/issues/54883
98            (
99                JsVariant::Integer32(_) | JsVariant::Float64(_),
100                JsVariant::String(_) | JsVariant::Boolean(_),
101            )
102            | (JsVariant::String(_), JsVariant::Integer32(_) | JsVariant::Float64(_)) => {
103                let x = self.to_number(context)?;
104                let y = other.to_number(context)?;
105                Number::equal(x, y)
106            }
107
108            // 6. If Type(x) is BigInt and Type(y) is String, then
109            //    a. Let n be ! StringToBigInt(y).
110            //    b. If n is NaN, return false.
111            //    c. Return the result of the comparison x == n.
112            (JsVariant::BigInt(a), JsVariant::String(b)) => JsBigInt::from_js_string(&b) == Some(a),
113
114            // 7. If Type(x) is String and Type(y) is BigInt, return the result of the comparison y == x.
115            (JsVariant::String(a), JsVariant::BigInt(b)) => JsBigInt::from_js_string(&a) == Some(b),
116
117            // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.
118            (JsVariant::Boolean(x), _) => {
119                return other.equals(&JsValue::new(i32::from(x)), context);
120            }
121
122            // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
123            (_, JsVariant::Boolean(y)) => return self.equals(&JsValue::new(i32::from(y)), context),
124
125            // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result
126            // of the comparison x == ? ToPrimitive(y).
127            (
128                JsVariant::Object(_),
129                JsVariant::String(_)
130                | JsVariant::Float64(_)
131                | JsVariant::Integer32(_)
132                | JsVariant::BigInt(_)
133                | JsVariant::Symbol(_),
134            ) => {
135                let primitive = self.to_primitive(context, PreferredType::Default)?;
136                return Ok(primitive
137                    .equals(other, context)
138                    .expect("should not fail according to spec"));
139            }
140
141            // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result
142            // of the comparison ? ToPrimitive(x) == y.
143            (
144                JsVariant::String(_)
145                | JsVariant::Float64(_)
146                | JsVariant::Integer32(_)
147                | JsVariant::BigInt(_)
148                | JsVariant::Symbol(_),
149                JsVariant::Object(_),
150            ) => {
151                let primitive = other.to_primitive(context, PreferredType::Default)?;
152                return Ok(primitive
153                    .equals(self, context)
154                    .expect("should not fail according to spec"));
155            }
156
157            // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is Number and Type(y) is BigInt, then
158            //    a. If x or y are any of NaN, +∞, or -∞, return false.
159            //    b. If the mathematical value of x is equal to the mathematical value of y, return true; otherwise return false.
160            (JsVariant::BigInt(a), JsVariant::Float64(b)) => a == b,
161            (JsVariant::Float64(a), JsVariant::BigInt(b)) => a == b,
162            (JsVariant::BigInt(a), JsVariant::Integer32(b)) => a == b,
163            (JsVariant::Integer32(a), JsVariant::BigInt(b)) => a == b,
164
165            // 13. Return false.
166            _ => false,
167        })
168    }
169
170    /// Abstract non-equality comparison.
171    ///
172    /// This method is executed when doing abstract equality comparisons with the `!=` operator.
173    /// It uses [`Self::equals`] to perform the comparison and negates the result.
174    pub fn not_equals(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
175        Ok(!self.equals(other, context)?)
176    }
177
178    /// The internal comparison abstract operation SameValue(x, y),
179    /// where x and y are ECMAScript language values, produces true or false.
180    ///
181    /// More information:
182    ///  - [ECMAScript][spec]
183    ///
184    /// [spec]: https://tc39.es/ecma262/#sec-samevalue
185    #[must_use]
186    pub fn same_value(x: &Self, y: &Self) -> bool {
187        // 1. If Type(x) is different from Type(y), return false.
188        if x.get_type() != y.get_type() {
189            return false;
190        }
191
192        match (x.variant(), y.variant()) {
193            // 2. If Type(x) is Number or BigInt, then
194            //    a. Return ! Type(x)::SameValue(x, y).
195            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value(&x, &y),
196            (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value(x, y),
197            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::same_value(x, f64::from(y)),
198            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::same_value(f64::from(x), y),
199            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
200
201            // 3. Return ! SameValueNonNumeric(x, y).
202            (_, _) => Self::same_value_non_numeric(x, y),
203        }
204    }
205
206    /// The internal comparison abstract operation `SameValueZero(x, y)`,
207    /// where `x` and `y` are ECMAScript language values, produces `true` or `false`.
208    ///
209    /// `SameValueZero` differs from `SameValue` only in its treatment of `+0` and `-0`.
210    ///
211    /// More information:
212    ///  - [ECMAScript][spec]
213    ///
214    /// [spec]: https://tc39.es/ecma262/#sec-samevaluezero
215    #[must_use]
216    pub fn same_value_zero(x: &Self, y: &Self) -> bool {
217        if x.get_type() != y.get_type() {
218            return false;
219        }
220
221        match (x.variant(), y.variant()) {
222            // 2. If Type(x) is Number or BigInt, then
223            //    a. Return ! Type(x)::SameValueZero(x, y).
224            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => JsBigInt::same_value_zero(&x, &y),
225
226            (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::same_value_zero(x, y),
227            (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
228                Number::same_value_zero(x, f64::from(y))
229            }
230            (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
231                Number::same_value_zero(f64::from(x), y)
232            }
233            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x == y,
234
235            // 3. Return ! SameValueNonNumeric(x, y).
236            (_, _) => Self::same_value_non_numeric(x, y),
237        }
238    }
239
240    fn same_value_non_numeric(x: &Self, y: &Self) -> bool {
241        debug_assert!(x.get_type() == y.get_type());
242        match (x.variant(), y.variant()) {
243            (JsVariant::Null, JsVariant::Null) | (JsVariant::Undefined, JsVariant::Undefined) => {
244                true
245            }
246            (JsVariant::String(x), JsVariant::String(y)) => x == y,
247            (JsVariant::Boolean(x), JsVariant::Boolean(y)) => x == y,
248            (JsVariant::Object(x), JsVariant::Object(y)) => JsObject::equals(&x, &y),
249            (JsVariant::Symbol(x), JsVariant::Symbol(y)) => x == y,
250            _ => false,
251        }
252    }
253}