Skip to main content

boa_engine/value/
operations.rs

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