[gjs: 3/5] value: Support converting BigInt to (u)int64 values




commit 20da419bf09d0d73b4b08d58cbe2da9f22b13677
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date:   Thu Oct 8 04:25:26 2020 +0200

    value: Support converting BigInt to (u)int64 values
    
    BigInt values are now accepted as parameters to C functions that take
    64-bit integers.
    
    For now BigInts are only accepted for 64-bit parameters. We may allow
    BigInt for any parameter in the future (once we've checked that we don't
    go out-of-range), for now we assume that BigInt values are used only for
    functions taking a type that can mostly handle them.
    
    For bounds checks, given that we don't have a bigger value holder than
    (u)int64_t, we have to create a new wrapper to avoid taking the fast path
    in gjs_arg_set_from_js_value(), and without having to hack
    type_has_js_getter().
    
    As per this, now for 64bit integers we use TypeWrapper as the type holder
    that has conversion operators to the base type, and for which we can then
    add a js_value_to_c_checked() specialization.
    
    See: #271

 gi/arg-inl.h                            |  17 +----
 gi/js-value-inl.h                       |  74 ++++++++++++++++++++-
 gi/value.cpp                            |  26 ++++++--
 installed-tests/js/testGIMarshalling.js | 111 +++++++++++++++++++++++++++++---
 installed-tests/js/testGObjectClass.js  |  72 +++++++++++++++++++++
 installed-tests/js/testRegress.js       |  78 +++++++++++++++++++++-
 test/gjs-tests.cpp                      |   7 +-
 7 files changed, 353 insertions(+), 32 deletions(-)
---
diff --git a/gi/arg-inl.h b/gi/arg-inl.h
index 19de766b9..5fb19e3b1 100644
--- a/gi/arg-inl.h
+++ b/gi/arg-inl.h
@@ -173,19 +173,6 @@ template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
 
 // Implementation to store rounded (u)int64_t numbers into double
 
-template <typename BigT>
-[[nodiscard]] inline constexpr BigT max_safe_big_number() {
-    return (BigT(1) << std::numeric_limits<double>::digits) - 1;
-}
-
-template <typename BigT>
-[[nodiscard]] inline constexpr BigT min_safe_big_number() {
-    if constexpr (std::is_signed_v<BigT>)
-        return -(max_safe_big_number<BigT>());
-
-    return std::numeric_limits<BigT>::lowest();
-}
-
 template <typename BigT>
 [[nodiscard]] inline std::enable_if_t<std::is_integral_v<BigT> &&
                                           (std::numeric_limits<BigT>::max() >
@@ -194,8 +181,8 @@ template <typename BigT>
 gjs_arg_get_maybe_rounded(GIArgument* arg) {
     BigT val = gjs_arg_get<BigT>(arg);
 
-    if (val < min_safe_big_number<BigT>() ||
-        val > max_safe_big_number<BigT>()) {
+    if (val < Gjs::min_safe_big_number<BigT>() ||
+        val > Gjs::max_safe_big_number<BigT>()) {
         g_warning(
             "Value %s cannot be safely stored in a JS Number "
             "and may be rounded",
diff --git a/gi/js-value-inl.h b/gi/js-value-inl.h
index 1aa12bda2..ff2358d25 100644
--- a/gi/js-value-inl.h
+++ b/gi/js-value-inl.h
@@ -15,6 +15,7 @@
 #include <glib-object.h>
 #include <glib.h>
 
+#include <js/BigInt.h>
 #include <js/Conversions.h>
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
@@ -27,6 +28,17 @@
 
 namespace Gjs {
 
+template <typename T>
+struct TypeWrapper {
+    constexpr TypeWrapper() : m_value(0) {}
+    explicit constexpr TypeWrapper(T v) : m_value(v) {}
+    constexpr operator T() const { return m_value; }
+    constexpr operator T() { return m_value; }
+
+ private:
+    T m_value;
+};
+
 namespace JsValueHolder {
 
 template <typename T1, typename T2>
@@ -81,7 +93,9 @@ constexpr auto get_strict() {
 
 template <typename T>
 constexpr auto get_relaxed() {
-    if constexpr (type_fits<T, int32_t>())
+    if constexpr (std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>)
+        return TypeWrapper<T>{};
+    else if constexpr (type_fits<T, int32_t>())
         return int32_t{};
     else if constexpr (type_fits<T, uint16_t>())
         return uint32_t{};
@@ -135,12 +149,20 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
 template <>
 GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
     JSContext* cx, const JS::HandleValue& value, int64_t* out) {
+    if (value.isBigInt()) {
+        *out = JS::ToBigInt64(value.toBigInt());
+        return true;
+    }
     return JS::ToInt64(cx, value, out);
 }
 
 template <>
 GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
     JSContext* cx, const JS::HandleValue& value, uint64_t* out) {
+    if (value.isBigInt()) {
+        *out = JS::ToBigUint64(value.toBigInt());
+        return true;
+    }
     return JS::ToUint64(cx, value, out);
 }
 
@@ -194,6 +216,19 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(
     return true;
 }
 
+template <typename BigT>
+[[nodiscard]] inline constexpr BigT max_safe_big_number() {
+    return (BigT(1) << std::numeric_limits<double>::digits) - 1;
+}
+
+template <typename BigT>
+[[nodiscard]] inline constexpr BigT min_safe_big_number() {
+    if constexpr (std::is_signed_v<BigT>)
+        return -(max_safe_big_number<BigT>());
+
+    return std::numeric_limits<BigT>::lowest();
+}
+
 template <typename WantedType, typename T>
 GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
     JSContext* cx, const JS::HandleValue& value, T* out, bool* out_of_range) {
@@ -203,6 +238,27 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
                           std::numeric_limits<WantedType>::lowest(),
                   "Container can't contain wanted type");
 
+    if constexpr (std::is_same_v<WantedType, uint64_t> ||
+                  std::is_same_v<WantedType, int64_t>) {
+        if (out_of_range) {
+            JS::BigInt* bi = nullptr;
+            *out_of_range = false;
+
+            if (value.isBigInt()) {
+                bi = value.toBigInt();
+            } else if (value.isNumber()) {
+                bi = JS::NumberToBigInt(cx, value.toNumber());
+                if (!bi)
+                    return false;
+            }
+
+            if (bi) {
+                *out_of_range = Gjs::bigint_is_out_of_range(bi, out);
+                return true;
+            }
+        }
+    }
+
     if constexpr (std::is_same_v<WantedType, T>)
         return js_value_to_c(cx, value, out);
 
@@ -230,4 +286,20 @@ GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
     }
 }
 
+template <typename WantedType>
+GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked(
+    JSContext* cx, const JS::HandleValue& value, TypeWrapper<WantedType>* out,
+    bool* out_of_range) {
+    static_assert(std::is_integral_v<WantedType>);
+
+    WantedType wanted_out;
+    if (!js_value_to_c_checked<WantedType>(cx, value, &wanted_out,
+                                           out_of_range))
+        return false;
+
+    *out = TypeWrapper<WantedType>{wanted_out};
+
+    return true;
+}
+
 }  // namespace Gjs
diff --git a/gi/value.cpp b/gi/value.cpp
index 96f5cf8d0..4a0e27548 100644
--- a/gi/value.cpp
+++ b/gi/value.cpp
@@ -13,6 +13,7 @@
 #include <glib-object.h>
 #include <glib.h>
 
+#include <js/BigInt.h>
 #include <js/CharacterEncoding.h>
 #include <js/Conversions.h>
 #include <js/Exception.h>
@@ -321,6 +322,17 @@ static bool gjs_value_guess_g_type(JSContext* context, JS::Value value,
         *gtype_out = G_TYPE_BOOLEAN;
         return true;
     }
+    if (value.isBigInt()) {
+        // Assume that if the value is negative or within the int64_t limit,
+        // then we're handling a signed integer, otherwise unsigned.
+        int64_t ignored;
+        if (JS::BigIntIsNegative(value.toBigInt()) ||
+            JS::BigIntFits(value.toBigInt(), &ignored))
+            *gtype_out = G_TYPE_INT64;
+        else
+            *gtype_out = G_TYPE_UINT64;
+        return true;
+    }
     if (value.isObject()) {
         JS::RootedObject obj(context, &value.toObject());
         return gjs_gtype_get_actual_gtype(context, obj, gtype_out);
@@ -427,10 +439,13 @@ gjs_value_to_g_value_internal(JSContext      *context,
         }
     } else if (gtype == G_TYPE_INT64) {
         gint64 i;
-        if (Gjs::js_value_to_c(context, value, &i)) {
+        if (Gjs::js_value_to_c_checked<int64_t>(context, value, &i,
+                                                &out_of_range) &&
+            !out_of_range) {
             g_value_set_int64(gvalue, i);
         } else {
-            return throw_expect_type(context, value, "64-bit integer");
+            return throw_expect_type(context, value, "64-bit integer", 0,
+                                     out_of_range);
         }
     } else if (gtype == G_TYPE_DOUBLE) {
         gdouble d;
@@ -457,10 +472,13 @@ gjs_value_to_g_value_internal(JSContext      *context,
         }
     } else if (gtype == G_TYPE_UINT64) {
         guint64 i;
-        if (Gjs::js_value_to_c(context, value, &i)) {
+        if (Gjs::js_value_to_c_checked<uint64_t>(context, value, &i,
+                                                 &out_of_range) &&
+            !out_of_range) {
             g_value_set_uint64(gvalue, i);
         } else {
-            return throw_expect_type(context, value, "unsigned 64-bit integer");
+            return throw_expect_type(context, value, "unsigned 64-bit integer",
+                                     0, out_of_range);
         }
     } else if (gtype == G_TYPE_BOOLEAN) {
         /* JS::ToBoolean() can't fail */
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index 7d5249194..72eba0835 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -135,17 +135,33 @@ const Limits = {
         utype: 'size',
     },
 };
+const BigIntLimits = {
+    int64: {
+        min: -(2n ** 63n),
+        max: 2n ** 63n - 1n,
+        umax: 2n ** 64n - 1n,
+    },
+    long: {},
+    ssize: {
+        utype: 'size',
+    },
+};
+
 Object.assign(Limits.short, Limits.int16);
 Object.assign(Limits.int, Limits.int32);
 // Platform dependent sizes; expand definitions as needed
-if (GLib.SIZEOF_LONG === 8)
+if (GLib.SIZEOF_LONG === 8) {
     Object.assign(Limits.long, Limits.int64);
-else
+    Object.assign(BigIntLimits.long, BigIntLimits.int64);
+} else {
     Object.assign(Limits.long, Limits.int32);
-if (GLib.SIZEOF_SSIZE_T === 8)
+}
+if (GLib.SIZEOF_SSIZE_T === 8) {
     Object.assign(Limits.ssize, Limits.int64);
-else
+    Object.assign(BigIntLimits.ssize, BigIntLimits.int64);
+} else {
     Object.assign(Limits.ssize, Limits.int32);
+}
 
 // Functions for dealing with tests that require or return unsafe 64-bit ints,
 // until we get BigInts.
@@ -239,6 +255,21 @@ describe('Integer', function () {
     });
 });
 
+describe('BigInt', function () {
+    Object.entries(BigIntLimits).forEach(([type, {min, max, umax, utype = `u${type}`}]) => {
+        describe(`${type}-typed`, function () {
+            it('marshals signed value as an in parameter', function () {
+                expect(() => GIMarshallingTests[`${type}_in_max`](max)).not.toThrow();
+                expect(() => GIMarshallingTests[`${type}_in_min`](min)).not.toThrow();
+            });
+
+            it('marshals unsigned value as an in parameter', function () {
+                expect(() => GIMarshallingTests[`${utype}_in`](umax)).not.toThrow();
+            });
+        });
+    });
+});
+
 describe('Floating point', function () {
     const FloatLimits = {
         float: {
@@ -744,9 +775,10 @@ describe('GValue', function () {
         },
     });
 
-    xit('marshals as an int64 in parameter', function () {
-        expect(() => GIMarshallingTests.gvalue_int64_in(Limits.int64.max)).not.toThrow();
-    }).pend('https://gitlab.gnome.org/GNOME/gjs/issues/271');
+    it('marshals as an int64 in parameter', function () {
+        expect(() => GIMarshallingTests.gvalue_int64_in(BigIntLimits.int64.max))
+            .not.toThrow();
+    });
 
     it('type objects can be converted from primitive-like types', function () {
         expect(() => GIMarshallingTests.gvalue_in_with_type(42, GObject.Int))
@@ -1699,7 +1731,12 @@ describe('Interface', function () {
             Implements: [GIMarshallingTests.Interface3],
         }, class I3Impl extends GObject.Object {
             vfunc_test_variant_array_in(variantArray) {
-                this.stuff = variantArray.map(v => v.deepUnpack());
+                this.stuff = variantArray.map(v => {
+                    const bit64 = this.bigInt &&
+                        (v.is_of_type(new GLib.VariantType('t')) ||
+                        v.is_of_type(new GLib.VariantType('x')));
+                    return warn64(bit64, () => v.deepUnpack());
+                });
             }
         });
         const i3 = new I3Impl();
@@ -1707,8 +1744,22 @@ describe('Interface', function () {
             new GLib.Variant('b', true),
             new GLib.Variant('s', 'hello'),
             new GLib.Variant('i', 42),
+            new GLib.Variant('t', 43),
+            new GLib.Variant('x', 44),
+        ]);
+        expect(i3.stuff).toEqual([true, 'hello', 42, 43, 44]);
+
+        i3.bigInt = true;
+        i3.test_variant_array_in([
+            new GLib.Variant('x', BigIntLimits.int64.min),
+            new GLib.Variant('x', BigIntLimits.int64.max),
+            new GLib.Variant('t', BigIntLimits.int64.umax),
+        ]);
+        expect(i3.stuff).toEqual([
+            Limits.int64.min,
+            Limits.int64.max,
+            Limits.int64.umax,
         ]);
-        expect(i3.stuff).toEqual([true, 'hello', 42]);
     });
 });
 
@@ -1870,6 +1921,16 @@ describe('GObject properties', function () {
             expect(obj[`some_${type}`]).toEqual(value2);
         });
     }
+
+    function testPropertyGetSetBigInt(type, value1, value2) {
+        it(`gets and sets a ${type} property with a bigint`, function () {
+            obj[`some_${type}`] = value1;
+            expect(obj[`some_${type}`]).toEqual(Number(value1));
+            obj[`some_${type}`] = value2;
+            expect(obj[`some_${type}`]).toEqual(Number(value2));
+        });
+    }
+
     testPropertyGetSet('boolean', true, false);
     testPropertyGetSet('char', 42, 64);
     testPropertyGetSet('uchar', 42, 64);
@@ -1878,10 +1939,37 @@ describe('GObject properties', function () {
     testPropertyGetSet('long', 42, 64);
     testPropertyGetSet('ulong', 42, 64);
     testPropertyGetSet('int64', 42, 64);
+    testPropertyGetSet('int64', Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
+    testPropertyGetSetBigInt('int64', BigIntLimits.int64.min, BigIntLimits.int64.max);
     testPropertyGetSet('uint64', 42, 64);
+    testPropertyGetSetBigInt('uint64', BigIntLimits.int64.max, BigIntLimits.int64.umax);
     testPropertyGetSet('string', 'Gjs', 'is cool!',
         'https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/268');
 
+    it('get and sets out-of-range values throws', function () {
+        expect(() => {
+            obj.some_int64 = Limits.int64.max;
+        }).toThrowError(/out of range/);
+        expect(() => {
+            obj.some_int64 = BigIntLimits.int64.max + 1n;
+        }).toThrowError(/out of range/);
+        expect(() => {
+            obj.some_int64 = BigIntLimits.int64.min - 1n;
+        }).toThrowError(/out of range/);
+        expect(() => {
+            obj.some_int64 = BigIntLimits.int64.umax;
+        }).toThrowError(/out of range/);
+        expect(() => {
+            obj.some_int64 = -BigIntLimits.int64.umax;
+        }).toThrowError(/out of range/);
+        expect(() => {
+            obj.some_uint64 = Limits.int64.min;
+        }).toThrowError(/out of range/);
+        expect(() => {
+            obj.some_uint64 = BigIntLimits.int64.umax + 100n;
+        }).toThrowError(/out of range/);
+    });
+
     it('gets and sets a float property', function () {
         obj.some_float = Math.E;
         expect(obj.some_float).toBeCloseTo(Math.E);
@@ -1901,8 +1989,13 @@ describe('GObject properties', function () {
         new GIMarshallingTests.BoxedStruct({long_: 42}));
     testPropertyGetSet('boxed_glist', null, null);
     testPropertyGetSet('gvalue', 42, 'foo');
+    testPropertyGetSetBigInt('gvalue', BigIntLimits.int64.umax, BigIntLimits.int64.min);
     testPropertyGetSet('variant', new GLib.Variant('b', true),
         new GLib.Variant('s', 'hello'));
+    testPropertyGetSet('variant', new GLib.Variant('x', BigIntLimits.int64.min),
+        new GLib.Variant('x', BigIntLimits.int64.max));
+    testPropertyGetSet('variant', new GLib.Variant('t', BigIntLimits.int64.max),
+        new GLib.Variant('t', BigIntLimits.int64.umax));
     testPropertyGetSet('object', new GObject.Object(),
         new GIMarshallingTests.Object({int: 42}));
     testPropertyGetSet('flags', GIMarshallingTests.Flags.VALUE2,
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index 41e7a8e9e..ee07b61d8 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -153,6 +153,17 @@ const MyCustomInit = GObject.registerClass(class MyCustomInit extends GObject.Ob
     }
 });
 
+const BigIntLimits = {
+    int64: {
+        min: -0x7fff_ffff_ffff_ffffn - 1n,
+        max: 0x7fff_ffff_ffff_ffffn,
+    },
+    uint64: {
+        min: 0n,
+        max: 0xffff_ffff_ffff_ffffn,
+    },
+};
+
 const NoName = GObject.registerClass(class extends GObject.Object {});
 
 describe('GObject class with decorator', function () {
@@ -365,6 +376,67 @@ describe('GObject class with decorator', function () {
         expect(() => new InterfacePropObject({file})).not.toThrow();
     });
 
+    it('can have an int64 property', function () {
+        const PropInt64 = GObject.registerClass({
+            Properties: {
+                'int64': GObject.ParamSpec.int64('int64', 'int64', 'int64',
+                    GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
+                    BigIntLimits.int64.min, BigIntLimits.int64.max, 0),
+            },
+        }, class PropInt64 extends GObject.Object {});
+
+        let int64 = BigIntLimits.int64.max - 5n;
+        let obj = new PropInt64({int64});
+        expect(obj.int64).toEqual(Number(int64));
+
+        int64 = BigIntLimits.int64.min + 555n;
+        obj = new PropInt64({int64});
+        expect(obj.int64).toEqual(Number(int64));
+    });
+
+    it('can have a default int64 property', function () {
+        const defaultValue = BigIntLimits.int64.max - 1000n;
+        const PropInt64Init = GObject.registerClass({
+            Properties: {
+                'int64': GObject.ParamSpec.int64('int64', 'int64', 'int64',
+                    GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
+                    BigIntLimits.int64.min, BigIntLimits.int64.max,
+                    defaultValue),
+            },
+        }, class PropDefaultInt64Init extends GObject.Object {});
+
+        const obj = new PropInt64Init();
+        expect(obj.int64).toEqual(Number(defaultValue));
+    });
+
+    it('can have an uint64 property', function () {
+        const PropUint64 = GObject.registerClass({
+            Properties: {
+                'uint64': GObject.ParamSpec.uint64('uint64', 'uint64', 'uint64',
+                    GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
+                    0, BigIntLimits.uint64.max, 0),
+            },
+        }, class PropUint64 extends GObject.Object {});
+
+        const uint64 = BigIntLimits.uint64.max - 5n;
+        const obj = new PropUint64({uint64});
+        expect(obj.uint64).toEqual(Number(uint64));
+    });
+
+    it('can have a default uint64 property', function () {
+        const defaultValue = BigIntLimits.uint64.max;
+        const PropUint64Init = GObject.registerClass({
+            Properties: {
+                'uint64': GObject.ParamSpec.uint64('uint64', 'uint64', 'uint64',
+                    GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
+                    0n, BigIntLimits.uint64.max, defaultValue),
+            },
+        }, class PropDefaultUint64Init extends GObject.Object {});
+
+        const obj = new PropUint64Init();
+        expect(obj.uint64).toEqual(Number(defaultValue));
+    });
+
     it('can override a property from the parent class', function () {
         const OverrideObject = GObject.registerClass({
             Properties: {
diff --git a/installed-tests/js/testRegress.js b/installed-tests/js/testRegress.js
index 094d41509..91eff3544 100644
--- a/installed-tests/js/testRegress.js
+++ b/installed-tests/js/testRegress.js
@@ -10,6 +10,23 @@ const GLib = imports.gi.GLib;
 const Gio = imports.gi.Gio;
 const GObject = imports.gi.GObject;
 
+function expectWarn64(callable) {
+    GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_WARNING,
+        '*cannot be safely stored*');
+    const ret = callable();
+    GLib.test_assert_expected_messages_internal('Gjs',
+        'testRegress.js', 0, 'Ignore message');
+    return ret;
+}
+
+const bit64Types = ['uint64', 'int64'];
+if (GLib.SIZEOF_LONG === 8)
+    bit64Types.push('long', 'ulong');
+if (GLib.SIZEOF_SIZE_T === 8)
+    bit64Types.push('size');
+if (GLib.SIZEOF_SSIZE_T === 8)
+    bit64Types.push('ssize');
+
 describe('Life, the Universe and Everything', function () {
     it('includes null return value', function () {
         expect(Regress.test_return_allow_none()).toBeNull();
@@ -29,12 +46,25 @@ describe('Life, the Universe and Everything', function () {
             expect(Regress[method](42)).toBe(42);
             expect(Regress[method](-42)).toBe(-42);
             expect(Regress[method](undefined)).toBe(0);
+
+            if (bits >= 64) {
+                expect(Regress[method](42n)).toBe(42);
+                expect(Regress[method](-42n)).toBe(-42);
+            } else {
+                expect(() => Regress[method](42n)).toThrow();
+                expect(() => Regress[method](-42n)).toThrow();
+            }
         });
 
         it(`includes unsigned ${bits}-bit integers`, function () {
             const method = `test_uint${bits}`;
             expect(Regress[method](42)).toBe(42);
             expect(Regress[method](undefined)).toBe(0);
+
+            if (bits >= 64)
+                expect(Regress[method](42n)).toEqual(42);
+            else
+                expect(() => Regress[method](42n)).toThrow();
         });
     });
 
@@ -48,6 +78,14 @@ describe('Life, the Universe and Everything', function () {
                 expect(Number.isNaN(Regress[method](undefined))).toBeTruthy();
             else
                 expect(Regress[method](undefined)).toBe(0);
+
+            if (bit64Types.includes(type)) {
+                expect(Regress[method](42n)).toBe(42);
+                expect(Regress[method](-42n)).toBe(-42);
+            } else {
+                expect(() => Regress[method](42n)).toThrow();
+                expect(() => Regress[method](-42n)).toThrow();
+            }
         });
     });
 
@@ -56,17 +94,55 @@ describe('Life, the Universe and Everything', function () {
             const method = `test_${type}`;
             expect(Regress[method](42)).toBe(42);
             expect(Regress[method](undefined)).toBe(0);
+
+            if (bit64Types.includes(type))
+                expect(Regress[method](42n)).toBe(42);
+            else
+                expect(() => Regress[method](42n)).toThrow();
         });
     });
 
     describe('No implicit conversion to unsigned', function () {
         ['uint8', 'uint16', 'uint32', 'uint64', 'uint', 'size'].forEach(type => {
             it(`for ${type}`, function () {
-                expect(() => Regress[`test_${type}`](-42)).toThrow();
+                expect(() => Regress[`test_${type}`](-42)).toThrowError(/out of range/);
+
+                if (bit64Types.includes(type))
+                    expect(() => Regress[`test_${type}`](-42n)).toThrowError(/out of range/);
+                else
+                    expect(() => Regress[`test_${type}`](-42n)).toThrow();
             });
         });
     });
 
+    describe('(u)int64 numeric values', function () {
+        const minInt64 = -(2n ** 63n);
+        const maxInt64 = 2n ** 63n - 1n;
+        const maxUint64 = 2n ** 64n - 1n;
+
+        ['uint64', 'int64', 'long', 'ulong', 'size', 'ssize'].forEach(type => {
+            if (!bit64Types.includes(type))
+                return;
+            const signed = ['int64', 'long', 'ssize'].includes(type);
+            const limits = {
+                min: signed ? minInt64 : 0n,
+                max: signed ? maxInt64 : maxUint64,
+            };
+            const testFunc = Regress[`test_${type}`];
+
+            it(`can use numeric limits for ${type}`, function () {
+                expect(expectWarn64(() => testFunc(limits.max)))
+                    .toEqual(Number(limits.max));
+
+                if (signed) {
+                    expect(expectWarn64(() => testFunc(limits.min)))
+                        .toEqual(Number(limits.min));
+                }
+            });
+        });
+    });
+
+
     it('includes wide characters', function () {
         expect(Regress.test_unichar('c')).toBe('c');
         expect(Regress.test_unichar('')).toBe('');
diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp
index aaffc779a..329c081c3 100644
--- a/test/gjs-tests.cpp
+++ b/test/gjs-tests.cpp
@@ -33,6 +33,7 @@
 #include <mozilla/Span.h>  // for MakeStringSpan
 
 #include "gi/arg-inl.h"
+#include "gi/js-value-inl.h"
 #include "gjs/context.h"
 #include "gjs/error-types.h"
 #include "gjs/jsapi-util.h"
@@ -837,7 +838,8 @@ static void gjstest_test_safe_integer_max(GjsUnitTestFixture* fx, const void*) {
     g_assert_true(JS_GetProperty(fx->cx, number_class_object,
                                  "MAX_SAFE_INTEGER", &safe_value));
 
-    g_assert_cmpint(safe_value.toNumber(), ==, max_safe_big_number<int64_t>());
+    g_assert_cmpint(safe_value.toNumber(), ==,
+                    Gjs::max_safe_big_number<int64_t>());
 }
 
 static void gjstest_test_safe_integer_min(GjsUnitTestFixture* fx, const void*) {
@@ -849,7 +851,8 @@ static void gjstest_test_safe_integer_min(GjsUnitTestFixture* fx, const void*) {
     g_assert_true(JS_GetProperty(fx->cx, number_class_object,
                                  "MIN_SAFE_INTEGER", &safe_value));
 
-    g_assert_cmpint(safe_value.toNumber(), ==, min_safe_big_number<int64_t>());
+    g_assert_cmpint(safe_value.toNumber(), ==,
+                    Gjs::min_safe_big_number<int64_t>());
 }
 
 static void gjstest_test_args_set_get_unset() {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]