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}