Skip to main content

wasm_bindgen/
safe_integer.rs

1use alloc::boxed::Box;
2use core::convert::TryFrom;
3use core::fmt;
4use core::mem;
5use core::ops::{
6    Add, AddAssign, Deref, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
7};
8
9use crate::__rt::marker::ErasableGeneric;
10use crate::convert::{
11    FromWasmAbi, IntoWasmAbi, LongRefFromWasmAbi, MaybeFromWasmAbi, MaybeIntoWasmAbi,
12    NullableFromWasmAbi, NullableIntoWasmAbi, RefFromWasmAbi, TryFromJsValue, UpcastFrom,
13    VectorFromWasmAbi, VectorIntoWasmAbi, WasmSlice,
14};
15use crate::describe::{inform, WasmDescribe, WasmDescribeVector, F64, VECTOR};
16use crate::sys::{JsNullable, JsOption, Promising};
17use crate::{JsValue, Nullable};
18
19/// `Number.MAX_SAFE_INTEGER`, `2^53 - 1`.
20const MAX_SAFE: f64 = 9007199254740991.0;
21
22mod sealed {
23    pub trait Sealed: Copy + Default + Eq + Ord + core::hash::Hash + 'static {
24        const SIGNED: bool;
25        const MAX: Self;
26        const MIN: Self;
27        fn to_f64(self) -> f64;
28        fn from_f64(n: f64) -> Self;
29    }
30
31    impl Sealed for u64 {
32        const SIGNED: bool = false;
33        const MAX: u64 = (1 << 53) - 1;
34        const MIN: u64 = 0;
35        #[inline]
36        fn to_f64(self) -> f64 {
37            self as f64
38        }
39        #[inline]
40        fn from_f64(n: f64) -> u64 {
41            n as u64
42        }
43    }
44
45    impl Sealed for i64 {
46        const SIGNED: bool = true;
47        const MAX: i64 = (1 << 53) - 1;
48        const MIN: i64 = -((1 << 53) - 1);
49        #[inline]
50        fn to_f64(self) -> f64 {
51            self as f64
52        }
53        #[inline]
54        fn from_f64(n: f64) -> i64 {
55            n as i64
56        }
57    }
58}
59
60/// The integer types a [`SafeInteger`] can hold: `u64` and `i64`.
61///
62/// This trait is sealed.
63pub trait SafeIntegerPrimitive: sealed::Sealed {}
64impl SafeIntegerPrimitive for u64 {}
65impl SafeIntegerPrimitive for i64 {}
66
67/// A `u64` or `i64` that crosses the JS boundary as a `number` instead of a
68/// `BigInt`.
69///
70/// Many JS APIs type integer counts as `number`. A plain `u64` in a binding
71/// signature is a `BigInt` at the boundary, so `SafeInteger<u64>` is used
72/// instead to keep the Rust integer type while passing a `number`. In
73/// TypeScript it is `number`.
74///
75/// ```ignore
76/// use wasm_bindgen::SafeInteger;
77///
78/// #[wasm_bindgen]
79/// extern "C" {
80///     type QueueMetrics;
81///     #[wasm_bindgen(method, getter, js_name = backlogCount)]
82///     fn backlog_count(this: &QueueMetrics) -> SafeInteger<u64>;
83/// }
84///
85/// let count = metrics.backlog_count();
86/// if count > 10_000 { /* ... */ }
87/// let next: u64 = (count + 1).into();
88/// ```
89///
90/// It derefs to the inner integer, so its methods are available directly,
91/// and compares and does arithmetic with both itself and the inner type.
92///
93/// The value is expected to be within `±(2^53 - 1)` (`Number.MAX_SAFE_INTEGER`)
94/// and, for `u64`, non-negative, so that it is exactly representable as a JS
95/// `number`. Like the overflow checks on Rust's own integers, this is only
96/// checked with debug assertions enabled: constructing a value out of range,
97/// arithmetic leaving the range, or receiving a non-integer or out-of-range
98/// `number` from JS then panics. Without debug assertions an out-of-range
99/// value converts lossily, as an `as` cast would. [`SafeInteger::new`] and
100/// the `TryFrom<JsValue>` conversion always check.
101///
102/// `SafeInteger` is supported in every position its conversion is reached
103/// through: arguments and returns, `catch` results, `Option`, `Vec` and
104/// `Box<[_]>`, borrowed slices passed into Rust, closure arguments, async
105/// import results and generic positions on `experimental_generic_mono`
106/// imports. A borrowed slice `&[SafeInteger<_>]` cannot be passed out to JS,
107/// since the elements have to be converted; pass a `Vec` or `Box<[_]>`
108/// instead.
109#[repr(transparent)]
110#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct SafeInteger<T: SafeIntegerPrimitive>(T);
112
113/// A `u64` that crosses the JS boundary as a `number`. See [`SafeInteger`].
114pub type SafeU64 = SafeInteger<u64>;
115
116/// An `i64` that crosses the JS boundary as a `number`. See [`SafeInteger`].
117pub type SafeI64 = SafeInteger<i64>;
118
119#[inline]
120fn is_safe<T: SafeIntegerPrimitive>(n: f64) -> bool {
121    let min = if T::SIGNED { -MAX_SAFE } else { 0.0 };
122    // In range, the cast is exact exactly when `n` is an integer. `NaN`
123    // fails the range check.
124    n >= min && n <= MAX_SAFE && (n as i64) as f64 == n
125}
126
127impl<T: SafeIntegerPrimitive> SafeInteger<T> {
128    /// The largest safe integer, `2^53 - 1` (`Number.MAX_SAFE_INTEGER`).
129    pub const MAX: Self = SafeInteger(T::MAX);
130
131    /// The smallest safe integer: `0` for `u64`, `-(2^53 - 1)`
132    /// (`Number.MIN_SAFE_INTEGER`) for `i64`.
133    pub const MIN: Self = SafeInteger(T::MIN);
134
135    /// Wraps `value`, returning `None` if it is out of the safe integer range.
136    #[inline]
137    pub fn new(value: T) -> Option<Self> {
138        if value >= T::MIN && value <= T::MAX {
139            Some(SafeInteger(value))
140        } else {
141            None
142        }
143    }
144
145    #[inline]
146    fn checked(value: T) -> Self {
147        debug_assert!(
148            value >= T::MIN && value <= T::MAX,
149            "SafeInteger out of the safe integer range"
150        );
151        SafeInteger(value)
152    }
153
154    #[inline]
155    fn to_f64(self) -> f64 {
156        self.0.to_f64()
157    }
158
159    #[inline]
160    fn from_f64(n: f64) -> Self {
161        debug_assert!(is_safe::<T>(n), "{n} is not a safe integer");
162        SafeInteger(T::from_f64(n))
163    }
164}
165
166impl<T: SafeIntegerPrimitive> Deref for SafeInteger<T> {
167    type Target = T;
168
169    #[inline]
170    fn deref(&self) -> &T {
171        &self.0
172    }
173}
174
175impl<T: SafeIntegerPrimitive> From<T> for SafeInteger<T> {
176    #[inline]
177    fn from(value: T) -> Self {
178        Self::checked(value)
179    }
180}
181
182impl<T: SafeIntegerPrimitive> PartialEq<T> for SafeInteger<T> {
183    #[inline]
184    fn eq(&self, other: &T) -> bool {
185        self.0 == *other
186    }
187}
188
189impl<T: SafeIntegerPrimitive> PartialOrd<T> for SafeInteger<T> {
190    #[inline]
191    fn partial_cmp(&self, other: &T) -> Option<core::cmp::Ordering> {
192        Some(self.0.cmp(other))
193    }
194}
195
196impl<T: SafeIntegerPrimitive + fmt::Debug> fmt::Debug for SafeInteger<T> {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        fmt::Debug::fmt(&self.0, f)
199    }
200}
201
202impl<T: SafeIntegerPrimitive + fmt::Display> fmt::Display for SafeInteger<T> {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        fmt::Display::fmt(&self.0, f)
205    }
206}
207
208macro_rules! binops {
209    ($($imp:ident $method:ident $assign_imp:ident $assign_method:ident,)*) => ($(
210        impl<T: SafeIntegerPrimitive + $imp<Output = T>> $imp for SafeInteger<T> {
211            type Output = Self;
212
213            #[inline]
214            fn $method(self, rhs: Self) -> Self {
215                Self::checked($imp::$method(self.0, rhs.0))
216            }
217        }
218
219        impl<T: SafeIntegerPrimitive + $imp<Output = T>> $imp<T> for SafeInteger<T> {
220            type Output = Self;
221
222            #[inline]
223            fn $method(self, rhs: T) -> Self {
224                Self::checked($imp::$method(self.0, rhs))
225            }
226        }
227
228        impl<T: SafeIntegerPrimitive + $imp<Output = T>> $assign_imp for SafeInteger<T> {
229            #[inline]
230            fn $assign_method(&mut self, rhs: Self) {
231                *self = $imp::$method(*self, rhs);
232            }
233        }
234
235        impl<T: SafeIntegerPrimitive + $imp<Output = T>> $assign_imp<T> for SafeInteger<T> {
236            #[inline]
237            fn $assign_method(&mut self, rhs: T) {
238                *self = $imp::$method(*self, rhs);
239            }
240        }
241    )*)
242}
243
244binops! {
245    Add add AddAssign add_assign,
246    Sub sub SubAssign sub_assign,
247    Mul mul MulAssign mul_assign,
248    Div div DivAssign div_assign,
249    Rem rem RemAssign rem_assign,
250}
251
252impl Neg for SafeInteger<i64> {
253    type Output = Self;
254
255    #[inline]
256    fn neg(self) -> Self {
257        SafeInteger(-self.0)
258    }
259}
260
261macro_rules! concrete {
262    ($($t:ident)*) => ($(
263        impl From<SafeInteger<$t>> for $t {
264            #[inline]
265            fn from(value: SafeInteger<$t>) -> $t {
266                value.0
267            }
268        }
269
270        impl PartialEq<SafeInteger<$t>> for $t {
271            #[inline]
272            fn eq(&self, other: &SafeInteger<$t>) -> bool {
273                *self == other.0
274            }
275        }
276
277        impl PartialOrd<SafeInteger<$t>> for $t {
278            #[inline]
279            fn partial_cmp(&self, other: &SafeInteger<$t>) -> Option<core::cmp::Ordering> {
280                Some(self.cmp(&other.0))
281            }
282        }
283    )*)
284}
285
286concrete!(u64 i64);
287
288impl<T: SafeIntegerPrimitive> WasmDescribe for SafeInteger<T> {
289    #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
290    fn describe() {
291        inform(F64)
292    }
293}
294
295impl<T: SafeIntegerPrimitive> IntoWasmAbi for SafeInteger<T> {
296    type Abi = f64;
297
298    #[inline]
299    fn into_abi(self) -> f64 {
300        self.to_f64()
301    }
302}
303
304impl<T: SafeIntegerPrimitive> FromWasmAbi for SafeInteger<T> {
305    type Abi = f64;
306
307    #[inline]
308    unsafe fn from_abi(js: f64) -> Self {
309        Self::from_f64(js)
310    }
311}
312
313// `Option` and `Nullable` take `f64`'s presence-tagged ABI rather than a
314// sentinel, since every safe integer is a valid value.
315impl<T: SafeIntegerPrimitive> MaybeIntoWasmAbi for SafeInteger<T> {
316    type MaybeAbi = <f64 as MaybeIntoWasmAbi>::MaybeAbi;
317
318    #[inline]
319    fn into_maybe_abi(self) -> Self::MaybeAbi {
320        self.to_f64().into_maybe_abi()
321    }
322
323    #[inline]
324    fn none() -> Self::MaybeAbi {
325        <f64 as MaybeIntoWasmAbi>::none()
326    }
327}
328
329impl<T: SafeIntegerPrimitive> NullableIntoWasmAbi for SafeInteger<T> {
330    #[inline]
331    fn null() -> Self::MaybeAbi {
332        <f64 as NullableIntoWasmAbi>::null()
333    }
334}
335
336impl<T: SafeIntegerPrimitive> MaybeFromWasmAbi for SafeInteger<T> {
337    type MaybeAbi = <f64 as MaybeFromWasmAbi>::MaybeAbi;
338
339    #[inline]
340    unsafe fn from_maybe_abi(js: Self::MaybeAbi) -> Self {
341        Self::from_f64(f64::from_maybe_abi(js))
342    }
343
344    #[inline]
345    fn is_none(js: &Self::MaybeAbi) -> bool {
346        <f64 as MaybeFromWasmAbi>::is_none(js)
347    }
348}
349
350impl<T: SafeIntegerPrimitive> NullableFromWasmAbi for SafeInteger<T> {
351    #[inline]
352    fn is_null(js: &Self::MaybeAbi) -> bool {
353        <f64 as NullableFromWasmAbi>::is_null(js)
354    }
355}
356
357// A borrowed `SafeInteger` crosses by copy, like the other scalars.
358impl<T: SafeIntegerPrimitive> IntoWasmAbi for &SafeInteger<T> {
359    type Abi = f64;
360
361    #[inline]
362    fn into_abi(self) -> f64 {
363        (*self).into_abi()
364    }
365}
366
367impl<T: SafeIntegerPrimitive> MaybeIntoWasmAbi for &SafeInteger<T> {
368    type MaybeAbi = <f64 as MaybeIntoWasmAbi>::MaybeAbi;
369
370    #[inline]
371    fn into_maybe_abi(self) -> Self::MaybeAbi {
372        (*self).into_maybe_abi()
373    }
374
375    #[inline]
376    fn none() -> Self::MaybeAbi {
377        <f64 as MaybeIntoWasmAbi>::none()
378    }
379}
380
381impl<T: SafeIntegerPrimitive> NullableIntoWasmAbi for &SafeInteger<T> {
382    #[inline]
383    fn null() -> Self::MaybeAbi {
384        <f64 as NullableIntoWasmAbi>::null()
385    }
386}
387
388// Vectors cross as `Float64Array`s. `SafeInteger<T>` has the size and
389// alignment of `f64`, so each element is converted in place and the
390// allocation is handed over as-is.
391const _: () = assert!(
392    mem::size_of::<SafeInteger<u64>>() == mem::size_of::<f64>()
393        && mem::align_of::<SafeInteger<u64>>() == mem::align_of::<f64>()
394        && mem::size_of::<SafeInteger<i64>>() == mem::size_of::<f64>()
395        && mem::align_of::<SafeInteger<i64>>() == mem::align_of::<f64>()
396);
397
398impl<T: SafeIntegerPrimitive> WasmDescribeVector for SafeInteger<T> {
399    #[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
400    fn describe_vector() {
401        inform(VECTOR);
402        inform(F64);
403    }
404}
405
406impl<T: SafeIntegerPrimitive> VectorIntoWasmAbi for SafeInteger<T> {
407    type Abi = WasmSlice;
408
409    fn vector_into_abi(vector: Box<[Self]>) -> WasmSlice {
410        let len = vector.len();
411        let ptr = Box::into_raw(vector) as *mut Self;
412        for i in 0..len {
413            unsafe {
414                let elem = ptr.add(i);
415                let n = (*elem).to_f64();
416                elem.cast::<f64>().write(n);
417            }
418        }
419        let floats =
420            unsafe { Box::from_raw(core::ptr::slice_from_raw_parts_mut(ptr.cast::<f64>(), len)) };
421        f64::vector_into_abi(floats)
422    }
423}
424
425impl<T: SafeIntegerPrimitive> VectorFromWasmAbi for SafeInteger<T> {
426    type Abi = WasmSlice;
427
428    unsafe fn vector_from_abi(js: WasmSlice) -> Box<[Self]> {
429        let floats = f64::vector_from_abi(js);
430        let len = floats.len();
431        let ptr = Box::into_raw(floats) as *mut f64;
432        for i in 0..len {
433            let elem = ptr.add(i);
434            let n = Self::from_f64(*elem);
435            elem.cast::<Self>().write(n);
436        }
437        Box::from_raw(core::ptr::slice_from_raw_parts_mut(ptr.cast::<Self>(), len))
438    }
439}
440
441impl<T: SafeIntegerPrimitive> RefFromWasmAbi for [SafeInteger<T>] {
442    type Abi = WasmSlice;
443    type Anchor = Box<[SafeInteger<T>]>;
444
445    #[inline]
446    unsafe fn ref_from_abi(js: WasmSlice) -> Self::Anchor {
447        <Box<[SafeInteger<T>]>>::from_abi(js)
448    }
449}
450
451impl<T: SafeIntegerPrimitive> LongRefFromWasmAbi for [SafeInteger<T>] {
452    type Abi = WasmSlice;
453    type Anchor = Box<[SafeInteger<T>]>;
454
455    #[inline]
456    unsafe fn long_ref_from_abi(js: WasmSlice) -> Self::Anchor {
457        <Box<[SafeInteger<T>]>>::from_abi(js)
458    }
459}
460
461unsafe impl<T: SafeIntegerPrimitive> ErasableGeneric for SafeInteger<T> {
462    type Repr = Self;
463}
464
465impl<T: SafeIntegerPrimitive> Promising for SafeInteger<T> {
466    type Resolution = Self;
467}
468
469impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for SafeInteger<T> {}
470impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for f64 {}
471impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for JsOption<f64> {}
472impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for JsNullable<f64> {}
473impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for JsValue {}
474impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for JsOption<JsValue> {}
475impl<T: SafeIntegerPrimitive> UpcastFrom<SafeInteger<T>> for JsNullable<JsValue> {}
476impl<T: SafeIntegerPrimitive, Target: UpcastFrom<SafeInteger<T>>> UpcastFrom<SafeInteger<T>>
477    for Option<Target>
478{
479}
480impl<T: SafeIntegerPrimitive, Target: UpcastFrom<SafeInteger<T>>> UpcastFrom<SafeInteger<T>>
481    for Nullable<Target>
482{
483}
484
485impl<T: SafeIntegerPrimitive> From<SafeInteger<T>> for JsValue {
486    #[inline]
487    fn from(n: SafeInteger<T>) -> JsValue {
488        JsValue::from_f64(n.to_f64())
489    }
490}
491
492impl<T: SafeIntegerPrimitive> PartialEq<SafeInteger<T>> for JsValue {
493    #[inline]
494    fn eq(&self, other: &SafeInteger<T>) -> bool {
495        self.as_f64() == Some(other.to_f64())
496    }
497}
498
499// Unlike the ABI conversion, this always checks: a value that is not a safe
500// integer is an `Err`.
501impl<T: SafeIntegerPrimitive> TryFromJsValue for SafeInteger<T> {
502    #[inline]
503    fn try_from_js_value_ref(value: &JsValue) -> Option<Self> {
504        let n = value.as_f64()?;
505        if is_safe::<T>(n) {
506            Some(SafeInteger(T::from_f64(n)))
507        } else {
508            None
509        }
510    }
511}
512
513impl<T: SafeIntegerPrimitive> TryFrom<JsValue> for SafeInteger<T> {
514    type Error = JsValue;
515
516    #[inline]
517    fn try_from(value: JsValue) -> Result<Self, JsValue> {
518        Self::try_from_js_value(value)
519    }
520}
521
522impl<T: SafeIntegerPrimitive> TryFrom<&JsValue> for SafeInteger<T> {
523    type Error = JsValue;
524
525    #[inline]
526    fn try_from(value: &JsValue) -> Result<Self, JsValue> {
527        Self::try_from_js_value_ref(value).ok_or_else(|| value.clone())
528    }
529}