Skip to main content

boa_engine/value/
operations.rs

1use crate::{
2    Context, JsBigInt, JsResult, JsValue, JsVariant,
3    builtins::{
4        Number,
5        math::Math,
6        number::{f64_to_int32, f64_to_uint32},
7    },
8    error::JsNativeError,
9    js_string,
10    value::{JsSymbol, Numeric, PreferredType},
11};
12
13impl JsValue {
14    /// Perform the binary `+` operator on the value and return the result.
15    pub fn add(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
16        Ok(match (self.variant(), other.variant()) {
17            // Fast path:
18            // Numeric add
19            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
20                .checked_add(y)
21                .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new),
22            (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x + y),
23            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) + y),
24            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x + f64::from(y)),
25            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::add(&x, &y)),
26
27            // String concat
28            (JsVariant::String(x), JsVariant::String(y)) => Self::from(js_string!(&x, &y)),
29
30            // Slow path:
31            (_, _) => {
32                let x = self.to_primitive(context, PreferredType::Default)?;
33                let y = other.to_primitive(context, PreferredType::Default)?;
34                match (x.variant(), y.variant()) {
35                    (JsVariant::String(x), _) => Self::from(js_string!(&x, &y.to_string(context)?)),
36                    (_, JsVariant::String(y)) => Self::from(js_string!(&x.to_string(context)?, &y)),
37                    (_, _) => {
38                        match (x.to_numeric(context)?, y.to_numeric(context)?) {
39                            (Numeric::Number(x), Numeric::Number(y)) => Self::new(x + y),
40                            (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
41                                Self::new(JsBigInt::add(x, y))
42                            }
43                            (_, _) => return Err(JsNativeError::typ()
44                                .with_message(
45                                    "cannot mix BigInt and other types, use explicit conversions",
46                                )
47                                .into()),
48                        }
49                    }
50                }
51            }
52        })
53    }
54
55    /// Perform the binary `-` operator on the value and return the result.
56    pub fn sub(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
57        Ok(match (self.variant(), other.variant()) {
58            // Fast path:
59            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
60                .checked_sub(y)
61                .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new),
62            (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x - y),
63            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) - y),
64            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x - f64::from(y)),
65
66            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::sub(&x, &y)),
67
68            // Slow path:
69            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
70                (Numeric::Number(a), Numeric::Number(b)) => Self::new(a - b),
71                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::sub(x, y)),
72                (_, _) => {
73                    return Err(JsNativeError::typ()
74                        .with_message("cannot mix BigInt and other types, use explicit conversions")
75                        .into());
76                }
77            },
78        })
79    }
80
81    /// Perform the binary `*` operator on the value and return the result.
82    pub fn mul(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
83        Ok(match (self.variant(), other.variant()) {
84            // Fast path:
85            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
86                .checked_mul(y)
87                // Check for the special case of `0 * -N` which must produce `-0.0`
88                .filter(|v| *v != 0 || i32::min(x, y) >= 0)
89                .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new),
90            (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x * y),
91            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) * y),
92            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x * f64::from(y)),
93
94            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::mul(&x, &y)),
95
96            // Slow path:
97            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
98                (Numeric::Number(a), Numeric::Number(b)) => Self::new(a * b),
99                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => Self::new(JsBigInt::mul(x, y)),
100                (_, _) => {
101                    return Err(JsNativeError::typ()
102                        .with_message("cannot mix BigInt and other types, use explicit conversions")
103                        .into());
104                }
105            },
106        })
107    }
108
109    /// Perform the binary `/` operator on the value and return the result.
110    pub fn div(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
111        Ok(match (self.variant(), other.variant()) {
112            // Fast path:
113            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => x
114                .checked_div(y)
115                .filter(|div| y * div == x)
116                .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new),
117            (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(x / y),
118            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(f64::from(x) / y),
119            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(x / f64::from(y)),
120
121            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => {
122                if y.is_zero() {
123                    return Err(JsNativeError::range()
124                        .with_message("BigInt division by zero")
125                        .into());
126                }
127                Self::new(JsBigInt::div(&x, &y))
128            }
129
130            // Slow path:
131            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
132                (Numeric::Number(a), Numeric::Number(b)) => Self::new(a / b),
133                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
134                    if y.is_zero() {
135                        return Err(JsNativeError::range()
136                            .with_message("BigInt division by zero")
137                            .into());
138                    }
139                    Self::new(JsBigInt::div(x, y))
140                }
141                (_, _) => {
142                    return Err(JsNativeError::typ()
143                        .with_message("cannot mix BigInt and other types, use explicit conversions")
144                        .into());
145                }
146            },
147        })
148    }
149
150    /// Perform the binary `%` operator on the value and return the result.
151    pub fn rem(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
152        Ok(match (self.variant(), other.variant()) {
153            // Fast path:
154            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
155                if y == 0 {
156                    Self::nan()
157                } else {
158                    match x % y {
159                        rem if rem == 0 && x < 0 => Self::new(-0.0),
160                        rem => Self::new(rem),
161                    }
162                }
163            }
164            (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new((x % y).copysign(x)),
165            (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
166                let x = f64::from(x);
167                Self::new((x % y).copysign(x))
168            }
169
170            (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
171                Self::new((x % f64::from(y)).copysign(x))
172            }
173
174            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => {
175                if y.is_zero() {
176                    return Err(JsNativeError::range()
177                        .with_message("BigInt division by zero")
178                        .into());
179                }
180                Self::new(JsBigInt::rem(&x, &y))
181            }
182
183            // Slow path:
184            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
185                (Numeric::Number(a), Numeric::Number(b)) => Self::new(a % b),
186                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
187                    if y.is_zero() {
188                        return Err(JsNativeError::range()
189                            .with_message("BigInt division by zero")
190                            .into());
191                    }
192                    Self::new(JsBigInt::rem(x, y))
193                }
194                (_, _) => {
195                    return Err(JsNativeError::typ()
196                        .with_message("cannot mix BigInt and other types, use explicit conversions")
197                        .into());
198                }
199            },
200        })
201    }
202
203    /// Perform the binary `**` operator on the value and return the result.
204    // NOTE: There are some cases in the spec where we have to compare floats
205    #[allow(clippy::float_cmp)]
206    pub fn pow(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
207        Ok(match (self.variant(), other.variant()) {
208            // Fast path:
209            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => u32::try_from(y)
210                .ok()
211                .and_then(|y| x.checked_pow(y))
212                .map_or_else(
213                    || Self::new(Math::exponentiate(f64::from(x), f64::from(y))),
214                    Self::new,
215                ),
216            (JsVariant::Float64(x), JsVariant::Float64(y)) => Self::new(Math::exponentiate(x, y)),
217            (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
218                Self::new(Math::exponentiate(f64::from(x), y))
219            }
220            (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
221                Self::new(Math::exponentiate(x, f64::from(y)))
222            }
223            (JsVariant::BigInt(a), JsVariant::BigInt(b)) => Self::new(JsBigInt::pow(&a, &b)?),
224
225            // Slow path:
226            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
227                (Numeric::Number(a), Numeric::Number(b)) => Self::new(Math::exponentiate(a, b)),
228                (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => Self::new(JsBigInt::pow(a, b)?),
229                (_, _) => {
230                    return Err(JsNativeError::typ()
231                        .with_message("cannot mix BigInt and other types, use explicit conversions")
232                        .into());
233                }
234            },
235        })
236    }
237
238    /// Perform the binary `&` operator on the value and return the result.
239    pub fn bitand(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
240        Ok(match (self.variant(), other.variant()) {
241            // Fast path:
242            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x & y),
243            (JsVariant::Float64(x), JsVariant::Float64(y)) => {
244                Self::new(f64_to_int32(x) & f64_to_int32(y))
245            }
246            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x & f64_to_int32(y)),
247            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) & y),
248
249            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitand(&x, &y)),
250
251            // Slow path:
252            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
253                (Numeric::Number(a), Numeric::Number(b)) => {
254                    Self::new(f64_to_int32(a) & f64_to_int32(b))
255                }
256                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
257                    Self::new(JsBigInt::bitand(x, y))
258                }
259                (_, _) => {
260                    return Err(JsNativeError::typ()
261                        .with_message("cannot mix BigInt and other types, use explicit conversions")
262                        .into());
263                }
264            },
265        })
266    }
267
268    /// Perform the binary `|` operator on the value and return the result.
269    pub fn bitor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
270        Ok(match (self.variant(), other.variant()) {
271            // Fast path:
272            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x | y),
273            (JsVariant::Float64(x), JsVariant::Float64(y)) => {
274                Self::new(f64_to_int32(x) | f64_to_int32(y))
275            }
276            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x | f64_to_int32(y)),
277            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) | y),
278
279            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitor(&x, &y)),
280
281            // Slow path:
282            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
283                (Numeric::Number(a), Numeric::Number(b)) => {
284                    Self::new(f64_to_int32(a) | f64_to_int32(b))
285                }
286                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
287                    Self::new(JsBigInt::bitor(x, y))
288                }
289                (_, _) => {
290                    return Err(JsNativeError::typ()
291                        .with_message("cannot mix BigInt and other types, use explicit conversions")
292                        .into());
293                }
294            },
295        })
296    }
297
298    /// Perform the binary `^` operator on the value and return the result.
299    pub fn bitxor(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
300        Ok(match (self.variant(), other.variant()) {
301            // Fast path:
302            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => Self::new(x ^ y),
303            (JsVariant::Float64(x), JsVariant::Float64(y)) => {
304                Self::new(f64_to_int32(x) ^ f64_to_int32(y))
305            }
306            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Self::new(x ^ f64_to_int32(y)),
307            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Self::new(f64_to_int32(x) ^ y),
308
309            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => Self::new(JsBigInt::bitxor(&x, &y)),
310
311            // Slow path:
312            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
313                (Numeric::Number(a), Numeric::Number(b)) => {
314                    Self::new(f64_to_int32(a) ^ f64_to_int32(b))
315                }
316                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
317                    Self::new(JsBigInt::bitxor(x, y))
318                }
319                (_, _) => {
320                    return Err(JsNativeError::typ()
321                        .with_message("cannot mix BigInt and other types, use explicit conversions")
322                        .into());
323                }
324            },
325        })
326    }
327
328    /// Perform the binary `<<` operator on the value and return the result.
329    pub fn shl(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
330        Ok(match (self.variant(), other.variant()) {
331            // Fast path:
332            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
333                Self::new(x.wrapping_shl(y as u32))
334            }
335            (JsVariant::Float64(x), JsVariant::Float64(y)) => {
336                Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
337            }
338            (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
339                Self::new(x.wrapping_shl(f64_to_uint32(y)))
340            }
341            (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
342                Self::new(f64_to_int32(x).wrapping_shl(y as u32))
343            }
344
345            (JsVariant::BigInt(a), JsVariant::BigInt(b)) => {
346                Self::new(JsBigInt::shift_left(&a, &b)?)
347            }
348
349            // Slow path:
350            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
351                (Numeric::Number(x), Numeric::Number(y)) => {
352                    Self::new(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
353                }
354                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
355                    Self::new(JsBigInt::shift_left(x, y)?)
356                }
357                (_, _) => {
358                    return Err(JsNativeError::typ()
359                        .with_message("cannot mix BigInt and other types, use explicit conversions")
360                        .into());
361                }
362            },
363        })
364    }
365
366    /// Perform the binary `>>` operator on the value and return the result.
367    pub fn shr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
368        Ok(match (self.variant(), other.variant()) {
369            // Fast path:
370            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
371                Self::new(x.wrapping_shr(y as u32))
372            }
373            (JsVariant::Float64(x), JsVariant::Float64(y)) => {
374                Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
375            }
376            (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
377                Self::new(x.wrapping_shr(f64_to_uint32(y)))
378            }
379            (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
380                Self::new(f64_to_int32(x).wrapping_shr(y as u32))
381            }
382
383            (JsVariant::BigInt(a), JsVariant::BigInt(b)) => {
384                Self::new(JsBigInt::shift_right(&a, &b)?)
385            }
386
387            // Slow path:
388            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
389                (Numeric::Number(x), Numeric::Number(y)) => {
390                    Self::new(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
391                }
392                (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
393                    Self::new(JsBigInt::shift_right(x, y)?)
394                }
395                (_, _) => {
396                    return Err(JsNativeError::typ()
397                        .with_message("cannot mix BigInt and other types, use explicit conversions")
398                        .into());
399                }
400            },
401        })
402    }
403
404    /// Perform the binary `>>>` operator on the value and return the result.
405    pub fn ushr(&self, other: &Self, context: &mut Context) -> JsResult<Self> {
406        Ok(match (self.variant(), other.variant()) {
407            // Fast path:
408            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => {
409                Self::new((x as u32).wrapping_shr(y as u32))
410            }
411            (JsVariant::Float64(x), JsVariant::Float64(y)) => {
412                Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
413            }
414            (JsVariant::Integer32(x), JsVariant::Float64(y)) => {
415                Self::new((x as u32).wrapping_shr(f64_to_uint32(y)))
416            }
417            (JsVariant::Float64(x), JsVariant::Integer32(y)) => {
418                Self::new(f64_to_uint32(x).wrapping_shr(y as u32))
419            }
420
421            // Slow path:
422            (_, _) => match (self.to_numeric(context)?, other.to_numeric(context)?) {
423                (Numeric::Number(x), Numeric::Number(y)) => {
424                    Self::new(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
425                }
426                (Numeric::BigInt(_), Numeric::BigInt(_)) => {
427                    return Err(JsNativeError::typ()
428                        .with_message("BigInts have no unsigned right shift, use >> instead")
429                        .into());
430                }
431                (_, _) => {
432                    return Err(JsNativeError::typ()
433                        .with_message("cannot mix BigInt and other types, use explicit conversions")
434                        .into());
435                }
436            },
437        })
438    }
439
440    /// Abstract operation `InstanceofOperator ( V, target )`
441    ///
442    /// More information:
443    /// - [ECMAScript reference][spec]
444    ///
445    /// [spec]: https://tc39.es/ecma262/#sec-instanceofoperator
446    pub fn instance_of(&self, target: &Self, context: &mut Context) -> JsResult<bool> {
447        // 1. If Type(target) is not Object, throw a TypeError exception.
448        if !target.is_object() {
449            return Err(JsNativeError::typ()
450                .with_message(format!(
451                    "right-hand side of 'instanceof' should be an object, got `{}`",
452                    target.type_of()
453                ))
454                .into());
455        }
456
457        // 2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
458        match target.get_method(JsSymbol::has_instance(), context)? {
459            // 3. If instOfHandler is not undefined, then
460            Some(instance_of_handler) => {
461                // a. Return ! ToBoolean(? Call(instOfHandler, target, « V »)).
462                Ok(instance_of_handler
463                    .call(target, std::slice::from_ref(self), context)?
464                    .to_boolean())
465            }
466            None if target.is_callable() => {
467                // 5. Return ? OrdinaryHasInstance(target, V).
468                Self::ordinary_has_instance(target, self, context)
469            }
470            None => {
471                // 4. If IsCallable(target) is false, throw a TypeError exception.
472                Err(JsNativeError::typ()
473                    .with_message("right-hand side of 'instanceof' is not callable")
474                    .into())
475            }
476        }
477    }
478
479    /// Returns the negated value.
480    pub fn neg(&self, context: &mut Context) -> JsResult<Self> {
481        Ok(match self.variant() {
482            JsVariant::Symbol(_) | JsVariant::Undefined => Self::new(f64::NAN),
483            JsVariant::Object(_) => Self::new(
484                self.to_numeric_number(context)
485                    .map_or(f64::NAN, std::ops::Neg::neg),
486            ),
487            JsVariant::String(str) => Self::new(-str.to_number()),
488            JsVariant::Float64(num) => Self::new(-num),
489            JsVariant::Integer32(0) | JsVariant::Boolean(false) | JsVariant::Null => {
490                Self::new(-0.0)
491            }
492            JsVariant::Integer32(num) => Self::new(-num),
493            JsVariant::Boolean(true) => Self::new(-1),
494            JsVariant::BigInt(x) => Self::new(JsBigInt::neg(&x)),
495        })
496    }
497
498    /// Returns the negated boolean value.
499    #[inline]
500    pub fn not(&self) -> JsResult<bool> {
501        Ok(!self.to_boolean())
502    }
503
504    /// Abstract relational comparison
505    ///
506    /// The comparison `x < y`, where `x` and `y` are values, produces `true`, `false`,
507    /// or `undefined` (which indicates that at least one operand is `NaN`).
508    ///
509    /// In addition to `x` and `y` the algorithm takes a Boolean flag named `LeftFirst` as a parameter.
510    /// The flag is used to control the order in which operations with potentially visible side-effects
511    /// are performed upon `x` and `y`. It is necessary because ECMAScript specifies left to right evaluation
512    /// of expressions. The default value of `LeftFirst` is `true` and indicates that the `x` parameter
513    /// corresponds to an expression that occurs to the left of the `y` parameter's corresponding expression.
514    ///
515    /// If `LeftFirst` is `false`, the reverse is the case and operations must be performed upon `y` before `x`.
516    ///
517    /// More Information:
518    ///  - [ECMAScript reference][spec]
519    ///
520    /// [spec]: https://tc39.es/ecma262/#sec-abstract-relational-comparison
521    pub fn abstract_relation(
522        &self,
523        other: &Self,
524        left_first: bool,
525        context: &mut Context,
526    ) -> JsResult<AbstractRelation> {
527        Ok(match (self.variant(), other.variant()) {
528            // Fast path (for some common operations):
529            (JsVariant::Integer32(x), JsVariant::Integer32(y)) => (x < y).into(),
530            (JsVariant::Integer32(x), JsVariant::Float64(y)) => Number::less_than(f64::from(x), y),
531            (JsVariant::Float64(x), JsVariant::Integer32(y)) => Number::less_than(x, f64::from(y)),
532            (JsVariant::Float64(x), JsVariant::Float64(y)) => Number::less_than(x, y),
533            (JsVariant::BigInt(x), JsVariant::BigInt(y)) => (x < y).into(),
534
535            // Slow path:
536            (_, _) => {
537                let (px, py) = if left_first {
538                    let px = self.to_primitive(context, PreferredType::Number)?;
539                    let py = other.to_primitive(context, PreferredType::Number)?;
540                    (px, py)
541                } else {
542                    // NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
543                    let py = other.to_primitive(context, PreferredType::Number)?;
544                    let px = self.to_primitive(context, PreferredType::Number)?;
545                    (px, py)
546                };
547
548                match (px.variant(), py.variant()) {
549                    (JsVariant::String(x), JsVariant::String(y)) => (x < y).into(),
550                    (JsVariant::BigInt(x), JsVariant::String(y)) => JsBigInt::from_js_string(&y)
551                        .map_or(AbstractRelation::Undefined, |y| (x < y).into()),
552                    (JsVariant::String(x), JsVariant::BigInt(y)) => JsBigInt::from_js_string(&x)
553                        .map_or(AbstractRelation::Undefined, |x| (x < y).into()),
554                    (_, _) => match (px.to_numeric(context)?, py.to_numeric(context)?) {
555                        (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y),
556                        (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
557                        (Numeric::BigInt(ref x), Numeric::Number(y)) => {
558                            if y.is_nan() {
559                                return Ok(AbstractRelation::Undefined);
560                            }
561                            if y.is_infinite() {
562                                return Ok(y.is_sign_positive().into());
563                            }
564
565                            if let Ok(y) = JsBigInt::try_from(y) {
566                                return Ok((x < &y).into());
567                            }
568
569                            (x.to_f64() < y).into()
570                        }
571                        (Numeric::Number(x), Numeric::BigInt(ref y)) => {
572                            if x.is_nan() {
573                                return Ok(AbstractRelation::Undefined);
574                            }
575                            if x.is_infinite() {
576                                return Ok(x.is_sign_negative().into());
577                            }
578
579                            if let Ok(x) = JsBigInt::try_from(x) {
580                                return Ok((&x < y).into());
581                            }
582
583                            (x < y.to_f64()).into()
584                        }
585                    },
586                }
587            }
588        })
589    }
590
591    /// The less than operator (`<`) returns `true` if the left operand is less than the right operand,
592    /// and `false` otherwise.
593    ///
594    /// More Information:
595    ///  - [MDN documentation][mdn]
596    ///  - [ECMAScript reference][spec]
597    ///
598    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than
599    /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
600    #[inline]
601    pub fn lt(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
602        match self.abstract_relation(other, true, context)? {
603            AbstractRelation::True => Ok(true),
604            AbstractRelation::False | AbstractRelation::Undefined => Ok(false),
605        }
606    }
607
608    /// The less than or equal operator (`<=`) returns `true` if the left operand is less than
609    /// or equal to the right operand, and `false` otherwise.
610    ///
611    /// More Information:
612    ///  - [MDN documentation][mdn]
613    ///  - [ECMAScript reference][spec]
614    ///
615    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Less_than_or_equal
616    /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
617    #[inline]
618    pub fn le(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
619        match other.abstract_relation(self, false, context)? {
620            AbstractRelation::False => Ok(true),
621            AbstractRelation::True | AbstractRelation::Undefined => Ok(false),
622        }
623    }
624
625    /// The greater than operator (`>`) returns `true` if the left operand is greater than
626    /// the right operand, and `false` otherwise.
627    ///
628    /// More Information:
629    ///  - [MDN documentation][mdn]
630    ///  - [ECMAScript reference][spec]
631    ///
632    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than
633    /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
634    #[inline]
635    pub fn gt(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
636        match other.abstract_relation(self, false, context)? {
637            AbstractRelation::True => Ok(true),
638            AbstractRelation::False | AbstractRelation::Undefined => Ok(false),
639        }
640    }
641
642    /// The greater than or equal operator (`>=`) returns `true` if the left operand is greater than
643    /// or equal to the right operand, and `false` otherwise.
644    ///
645    /// More Information:
646    ///  - [MDN documentation][mdn]
647    ///  - [ECMAScript reference][spec]
648    ///
649    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than_or_equal
650    /// [spec]: https://tc39.es/ecma262/#sec-relational-operators-runtime-semantics-evaluation
651    #[inline]
652    pub fn ge(&self, other: &Self, context: &mut Context) -> JsResult<bool> {
653        match self.abstract_relation(other, true, context)? {
654            AbstractRelation::False => Ok(true),
655            AbstractRelation::True | AbstractRelation::Undefined => Ok(false),
656        }
657    }
658
659    // ==== Numeric Fast Paths ====
660    //
661    // These methods provide optimized paths for binary operations at the opcode
662    // handler level. They use `self.0.as_integer32()` and `self.as_number_cheap()`
663    // (pure bit operations) instead of `variant()`, which avoids cloning pointer
664    // types (Object, String, Symbol, BigInt) for non-numeric values.
665    //
666    // Returns `Some(result)` if both operands are numeric, `None` otherwise
667    // (caller should fall back to the full method with type coercion).
668
669    /// Converts the value to a number if possible, using fast bit operations. This is
670    /// used for the fast operations to allow mixed i32/f64 values.
671    #[inline]
672    fn as_number_cheap(&self) -> Option<f64> {
673        if let Some(i) = self.0.as_integer32() {
674            Some(f64::from(i))
675        } else {
676            self.0.as_float64()
677        }
678    }
679
680    /// Fast path for the binary `+` operator (numeric only).
681    #[inline]
682    #[allow(clippy::float_cmp)]
683    pub(crate) fn add_fast(&self, other: &Self) -> Option<Self> {
684        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
685            return Some(
686                x.checked_add(y)
687                    .map_or_else(|| Self::new(f64::from(x) + f64::from(y)), Self::new),
688            );
689        }
690        let x = self.as_number_cheap()?;
691        let y = other.as_number_cheap()?;
692        Some(Self::new(x + y))
693    }
694
695    /// Fast path for the binary `-` operator.
696    #[inline]
697    pub(crate) fn sub_fast(&self, other: &Self) -> Option<Self> {
698        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
699            return Some(
700                x.checked_sub(y)
701                    .map_or_else(|| Self::new(f64::from(x) - f64::from(y)), Self::new),
702            );
703        }
704        let x = self.as_number_cheap()?;
705        let y = other.as_number_cheap()?;
706        Some(Self::new(x - y))
707    }
708
709    /// Fast path for the binary `*` operator.
710    #[inline]
711    pub(crate) fn mul_fast(&self, other: &Self) -> Option<Self> {
712        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
713            return Some(
714                x.checked_mul(y)
715                    .filter(|v| *v != 0 || i32::min(x, y) >= 0)
716                    .map_or_else(|| Self::new(f64::from(x) * f64::from(y)), Self::new),
717            );
718        }
719        let x = self.as_number_cheap()?;
720        let y = other.as_number_cheap()?;
721        Some(Self::new(x * y))
722    }
723
724    /// Fast path for the binary `/` operator.
725    #[inline]
726    #[allow(clippy::float_cmp)]
727    pub(crate) fn div_fast(&self, other: &Self) -> Option<Self> {
728        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
729            return Some(
730                x.checked_div(y)
731                    .filter(|div| y * div == x)
732                    .map_or_else(|| Self::new(f64::from(x) / f64::from(y)), Self::new),
733            );
734        }
735        let x = self.as_number_cheap()?;
736        let y = other.as_number_cheap()?;
737        Some(Self::new(x / y))
738    }
739
740    /// Fast path for the binary `%` operator.
741    #[inline]
742    pub(crate) fn rem_fast(&self, other: &Self) -> Option<Self> {
743        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
744            if y == 0 {
745                return Some(Self::nan());
746            }
747            return Some(match x % y {
748                rem if rem == 0 && x < 0 => Self::new(-0.0),
749                rem => Self::new(rem),
750            });
751        }
752        let x = self.as_number_cheap()?;
753        let y = other.as_number_cheap()?;
754        Some(Self::new((x % y).copysign(x)))
755    }
756
757    /// Fast path for the binary `**` operator.
758    #[inline]
759    #[allow(clippy::float_cmp)]
760    pub(crate) fn pow_fast(&self, other: &Self) -> Option<Self> {
761        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
762            return Some(
763                u32::try_from(y)
764                    .ok()
765                    .and_then(|y| x.checked_pow(y))
766                    .map_or_else(
767                        || Self::new(Math::exponentiate(f64::from(x), f64::from(y))),
768                        Self::new,
769                    ),
770            );
771        }
772        let x = self.as_number_cheap()?;
773        let y = other.as_number_cheap()?;
774        Some(Self::new(Math::exponentiate(x, y)))
775    }
776
777    /// Fast path for the binary `&` operator (i32 only).
778    #[inline]
779    pub(crate) fn bitand_fast(&self, other: &Self) -> Option<Self> {
780        let x = self.0.as_integer32()?;
781        let y = other.0.as_integer32()?;
782        Some(Self::new(x & y))
783    }
784
785    /// Fast path for the binary `|` operator (i32 only).
786    #[inline]
787    pub(crate) fn bitor_fast(&self, other: &Self) -> Option<Self> {
788        let x = self.0.as_integer32()?;
789        let y = other.0.as_integer32()?;
790        Some(Self::new(x | y))
791    }
792
793    /// Fast path for the binary `^` operator (i32 only).
794    #[inline]
795    pub(crate) fn bitxor_fast(&self, other: &Self) -> Option<Self> {
796        let x = self.0.as_integer32()?;
797        let y = other.0.as_integer32()?;
798        Some(Self::new(x ^ y))
799    }
800
801    /// Fast path for the binary `<<` operator (i32 only).
802    #[inline]
803    pub(crate) fn shl_fast(&self, other: &Self) -> Option<Self> {
804        let x = self.0.as_integer32()?;
805        let y = other.0.as_integer32()?;
806        Some(Self::new(x.wrapping_shl(y as u32)))
807    }
808
809    /// Fast path for the binary `>>` operator (i32 only).
810    #[inline]
811    pub(crate) fn shr_fast(&self, other: &Self) -> Option<Self> {
812        let x = self.0.as_integer32()?;
813        let y = other.0.as_integer32()?;
814        Some(Self::new(x.wrapping_shr(y as u32)))
815    }
816
817    /// Fast path for the binary `>>>` operator (i32 only).
818    #[inline]
819    pub(crate) fn ushr_fast(&self, other: &Self) -> Option<Self> {
820        let x = self.0.as_integer32()?;
821        let y = other.0.as_integer32()?;
822        Some(Self::new((x as u32).wrapping_shr(y as u32)))
823    }
824
825    /// Fast path for the `<` operator.
826    #[inline]
827    pub(crate) fn lt_fast(&self, other: &Self) -> Option<bool> {
828        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
829            return Some(x < y);
830        }
831        let x = self.as_number_cheap()?;
832        let y = other.as_number_cheap()?;
833        Some(x < y)
834    }
835
836    /// Fast path for the `<=` operator.
837    #[inline]
838    pub(crate) fn le_fast(&self, other: &Self) -> Option<bool> {
839        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
840            return Some(x <= y);
841        }
842        let x = self.as_number_cheap()?;
843        let y = other.as_number_cheap()?;
844        Some(x <= y)
845    }
846
847    /// Fast path for the `>` operator.
848    #[inline]
849    pub(crate) fn gt_fast(&self, other: &Self) -> Option<bool> {
850        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
851            return Some(x > y);
852        }
853        let x = self.as_number_cheap()?;
854        let y = other.as_number_cheap()?;
855        Some(x > y)
856    }
857
858    /// Fast path for the `>=` operator.
859    #[inline]
860    pub(crate) fn ge_fast(&self, other: &Self) -> Option<bool> {
861        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
862            return Some(x >= y);
863        }
864        let x = self.as_number_cheap()?;
865        let y = other.as_number_cheap()?;
866        Some(x >= y)
867    }
868
869    /// Fast path for the `==` operator (numeric only).
870    #[inline]
871    #[allow(clippy::float_cmp)]
872    pub(crate) fn equals_fast(&self, other: &Self) -> Option<Self> {
873        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
874            return Some(Self::new(x == y));
875        }
876        let x = self.as_number_cheap()?;
877        let y = other.as_number_cheap()?;
878        Some(Self::new(x == y))
879    }
880
881    /// Fast path for the `!=` operator (numeric only).
882    #[inline]
883    #[allow(clippy::float_cmp)]
884    pub(crate) fn not_equals_fast(&self, other: &Self) -> Option<Self> {
885        if let (Some(x), Some(y)) = (self.0.as_integer32(), other.0.as_integer32()) {
886            return Some(Self::new(x != y));
887        }
888        let x = self.as_number_cheap()?;
889        let y = other.as_number_cheap()?;
890        Some(Self::new(x != y))
891    }
892}
893
894/// The result of the [Abstract Relational Comparison][arc].
895///
896/// Comparison `x < y`, where `x` and `y` are values.
897/// It produces `true`, `false`, or `undefined`
898/// (which indicates that at least one operand is `NaN`).
899///
900/// [arc]: https://tc39.es/ecma262/#sec-abstract-relational-comparison
901#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
902pub enum AbstractRelation {
903    /// `x` is less than `y`
904    True,
905    /// `x` is **not** less than `y`
906    False,
907    /// Indicates that at least one operand is `NaN`
908    Undefined,
909}
910
911impl From<bool> for AbstractRelation {
912    #[inline]
913    fn from(value: bool) -> Self {
914        if value { Self::True } else { Self::False }
915    }
916}