boa/builtins/bigint/
mod.rs

1//! This module implements the global `BigInt` object.
2//!
3//! `BigInt` is a built-in object that provides a way to represent whole numbers larger
4//! than the largest number JavaScript can reliably represent with the Number primitive
5//! and represented by the `Number.MAX_SAFE_INTEGER` constant.
6//! `BigInt` can be used for arbitrarily large integers.
7//!
8//! More information:
9//!  - [ECMAScript reference][spec]
10//!  - [MDN documentation][mdn]
11//!
12//! [spec]: https://tc39.es/ecma262/#sec-bigint-objects
13//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
14
15use crate::{
16    builtins::{BuiltIn, JsArgs},
17    object::ConstructorBuilder,
18    property::Attribute,
19    symbol::WellKnownSymbols,
20    value::IntegerOrInfinity,
21    BoaProfiler, Context, JsBigInt, JsResult, JsValue,
22};
23#[cfg(test)]
24mod tests;
25
26/// `BigInt` implementation.
27#[derive(Debug, Clone, Copy)]
28pub struct BigInt;
29
30impl BuiltIn for BigInt {
31    const NAME: &'static str = "BigInt";
32
33    fn attribute() -> Attribute {
34        Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
35    }
36
37    fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
38        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
39
40        let to_string_tag = WellKnownSymbols::to_string_tag();
41
42        let bigint_object = ConstructorBuilder::with_standard_object(
43            context,
44            Self::constructor,
45            context.standard_objects().bigint_object().clone(),
46        )
47        .name(Self::NAME)
48        .length(Self::LENGTH)
49        .method(Self::to_string, "toString", 0)
50        .method(Self::value_of, "valueOf", 0)
51        .static_method(Self::as_int_n, "asIntN", 2)
52        .static_method(Self::as_uint_n, "asUintN", 2)
53        .callable(true)
54        .constructable(false)
55        .property(
56            to_string_tag,
57            Self::NAME,
58            Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
59        )
60        .build();
61
62        (Self::NAME, bigint_object.into(), Self::attribute())
63    }
64}
65
66impl BigInt {
67    /// The amount of arguments this function object takes.
68    pub(crate) const LENGTH: usize = 1;
69
70    /// `BigInt()`
71    ///
72    /// The `BigInt()` constructor is used to create BigInt objects.
73    ///
74    /// More information:
75    ///  - [ECMAScript reference][spec]
76    ///  - [MDN documentation][mdn]
77    ///
78    /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
79    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
80    fn constructor(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
81        let data = match args.get(0) {
82            Some(value) => value.to_bigint(context)?,
83            None => JsBigInt::zero(),
84        };
85        Ok(JsValue::new(data))
86    }
87
88    /// The abstract operation thisBigIntValue takes argument value.
89    ///
90    /// The phrase “this BigInt value” within the specification of a method refers to the
91    /// result returned by calling the abstract operation thisBigIntValue with the `this` value
92    /// of the method invocation passed as the argument.
93    ///
94    /// More information:
95    ///  - [ECMAScript reference][spec]
96    ///
97    /// [spec]: https://tc39.es/ecma262/#sec-thisbigintvalue
98    #[inline]
99    fn this_bigint_value(value: &JsValue, context: &mut Context) -> JsResult<JsBigInt> {
100        match value {
101            // 1. If Type(value) is BigInt, return value.
102            JsValue::BigInt(ref bigint) => return Ok(bigint.clone()),
103
104            // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
105            //    a. Assert: Type(value.[[BigIntData]]) is BigInt.
106            //    b. Return value.[[BigIntData]].
107            JsValue::Object(ref object) => {
108                if let Some(bigint) = object.borrow().as_bigint() {
109                    return Ok(bigint.clone());
110                }
111            }
112            _ => {}
113        }
114
115        // 3. Throw a TypeError exception.
116        Err(context.construct_type_error("'this' is not a BigInt"))
117    }
118
119    /// `BigInt.prototype.toString( [radix] )`
120    ///
121    /// The `toString()` method returns a string representing the specified BigInt object.
122    ///
123    /// More information:
124    ///  - [ECMAScript reference][spec]
125    ///  - [MDN documentation][mdn]
126    ///
127    /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring
128    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
129    #[allow(clippy::wrong_self_convention)]
130    pub(crate) fn to_string(
131        this: &JsValue,
132        args: &[JsValue],
133        context: &mut Context,
134    ) -> JsResult<JsValue> {
135        // 1. Let x be ? thisBigIntValue(this value).
136        let x = Self::this_bigint_value(this, context)?;
137
138        let radix = args.get_or_undefined(0);
139
140        // 2. If radix is undefined, let radixMV be 10.
141        let radix_mv = if radix.is_undefined() {
142            // 5. If radixMV = 10, return ! ToString(x).
143            // Note: early return optimization.
144            return Ok(x.to_string().into());
145        // 3. Else, let radixMV be ? ToIntegerOrInfinity(radix).
146        } else {
147            radix.to_integer_or_infinity(context)?
148        };
149
150        // 4. If radixMV < 2 or radixMV > 36, throw a RangeError exception.
151        let radix_mv = match radix_mv {
152            IntegerOrInfinity::Integer(i) if (2..=36).contains(&i) => i,
153            _ => {
154                return context.throw_range_error(
155                    "radix must be an integer at least 2 and no greater than 36",
156                )
157            }
158        };
159
160        // 5. If radixMV = 10, return ! ToString(x).
161        if radix_mv == 10 {
162            return Ok(x.to_string().into());
163        }
164
165        // 1. Let x be ? thisBigIntValue(this value).
166        // 6. Return the String representation of this Number value using the radix specified by radixMV.
167        //    Letters a-z are used for digits with values 10 through 35.
168        //    The precise algorithm is implementation-defined, however the algorithm should be a generalization of that specified in 6.1.6.2.23.
169        Ok(JsValue::new(x.to_string_radix(radix_mv as u32)))
170    }
171
172    /// `BigInt.prototype.valueOf()`
173    ///
174    /// The `valueOf()` method returns the wrapped primitive value of a Number object.
175    ///
176    /// More information:
177    ///  - [ECMAScript reference][spec]
178    ///  - [MDN documentation][mdn]
179    ///
180    /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof
181    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf
182    pub(crate) fn value_of(
183        this: &JsValue,
184        _: &[JsValue],
185        context: &mut Context,
186    ) -> JsResult<JsValue> {
187        Ok(JsValue::new(Self::this_bigint_value(this, context)?))
188    }
189
190    /// `BigInt.asIntN()`
191    ///
192    /// The `BigInt.asIntN()` method wraps the value of a `BigInt` to a signed integer between `-2**(width - 1)` and `2**(width-1) - 1`.
193    ///
194    /// [spec]: https://tc39.es/ecma262/#sec-bigint.asintn
195    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN
196    #[allow(clippy::wrong_self_convention)]
197    pub(crate) fn as_int_n(
198        _: &JsValue,
199        args: &[JsValue],
200        context: &mut Context,
201    ) -> JsResult<JsValue> {
202        let (modulo, bits) = Self::calculate_as_uint_n(args, context)?;
203
204        if bits > 0
205            && modulo >= JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(bits as i64 - 1), context)?
206        {
207            Ok(JsValue::new(JsBigInt::sub(
208                &modulo,
209                &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(bits as i64), context)?,
210            )))
211        } else {
212            Ok(JsValue::new(modulo))
213        }
214    }
215
216    /// `BigInt.asUintN()`
217    ///
218    /// The `BigInt.asUintN()` method wraps the value of a `BigInt` to an unsigned integer between `0` and `2**(width) - 1`.
219    ///
220    /// [spec]: https://tc39.es/ecma262/#sec-bigint.asuintn
221    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN
222    #[allow(clippy::wrong_self_convention)]
223    pub(crate) fn as_uint_n(
224        _: &JsValue,
225        args: &[JsValue],
226        context: &mut Context,
227    ) -> JsResult<JsValue> {
228        let (modulo, _) = Self::calculate_as_uint_n(args, context)?;
229
230        Ok(JsValue::new(modulo))
231    }
232
233    /// Helper function to wrap the value of a `BigInt` to an unsigned integer.
234    ///
235    /// This function expects the same arguments as `as_uint_n` and wraps the value of a `BigInt`.
236    /// Additionally to the wrapped unsigned value it returns the converted `bits` argument, so it
237    /// can be reused from the `as_int_n` method.
238    fn calculate_as_uint_n(args: &[JsValue], context: &mut Context) -> JsResult<(JsBigInt, u32)> {
239        use std::convert::TryFrom;
240
241        let bits_arg = args.get_or_undefined(0);
242        let bigint_arg = args.get_or_undefined(1);
243
244        let bits = bits_arg.to_index(context)?;
245        let bits = u32::try_from(bits).unwrap_or(u32::MAX);
246
247        let bigint = bigint_arg.to_bigint(context)?;
248
249        Ok((
250            JsBigInt::mod_floor(
251                &bigint,
252                &JsBigInt::pow(&JsBigInt::new(2), &JsBigInt::new(bits as i64), context)?,
253            ),
254            bits,
255        ))
256    }
257}