Skip to content

Commit a6571ee

Browse files
authored
Merge pull request #2709 from jbethune/master
Implement Ser+De for Saturating<T>
2 parents 6e38aff + 3d1b19e commit a6571ee

File tree

6 files changed

+134
-2
lines changed

6 files changed

+134
-2
lines changed

serde/build.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ fn main() {
6464
if minor < 64 {
6565
println!("cargo:rustc-cfg=no_core_cstr");
6666
}
67+
68+
// Support for core::num::Saturating and std::num::Saturating stabilized in Rust 1.74
69+
// https://blog.rust-lang.org/2023/11/16/Rust-1.74.0.html#stabilized-apis
70+
if minor < 74 {
71+
println!("cargo:rustc-cfg=no_core_num_saturating");
72+
}
6773
}
6874

6975
fn rustc_minor_version() -> Option<u32> {

serde/src/de/impls.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,73 @@ impl_deserialize_num! {
387387
num_as_self!(u8:visit_u8 u16:visit_u16 u32:visit_u32 u64:visit_u64);
388388
}
389389

390+
#[cfg(not(no_core_num_saturating))]
391+
macro_rules! visit_saturating {
392+
($primitive:ident, $ty:ident : $visit:ident) => {
393+
#[inline]
394+
fn $visit<E>(self, v: $ty) -> Result<Saturating<$primitive>, E>
395+
where
396+
E: Error,
397+
{
398+
let out: $primitive = core::convert::TryFrom::<$ty>::try_from(v).unwrap_or_else(|_| {
399+
#[allow(unused_comparisons)]
400+
if v < 0 {
401+
// never true for unsigned values
402+
$primitive::MIN
403+
} else {
404+
$primitive::MAX
405+
}
406+
});
407+
Ok(Saturating(out))
408+
}
409+
};
410+
}
411+
412+
macro_rules! impl_deserialize_saturating_num {
413+
($primitive:ident, $deserialize:ident ) => {
414+
#[cfg(not(no_core_num_saturating))]
415+
impl<'de> Deserialize<'de> for Saturating<$primitive> {
416+
#[inline]
417+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
418+
where
419+
D: Deserializer<'de>,
420+
{
421+
struct SaturatingVisitor;
422+
423+
impl<'de> Visitor<'de> for SaturatingVisitor {
424+
type Value = Saturating<$primitive>;
425+
426+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
427+
formatter.write_str("An integer with support for saturating semantics")
428+
}
429+
430+
visit_saturating!($primitive, u8:visit_u8);
431+
visit_saturating!($primitive, u16:visit_u16);
432+
visit_saturating!($primitive, u32:visit_u32);
433+
visit_saturating!($primitive, u64:visit_u64);
434+
visit_saturating!($primitive, i8:visit_i8);
435+
visit_saturating!($primitive, i16:visit_i16);
436+
visit_saturating!($primitive, i32:visit_i32);
437+
visit_saturating!($primitive, i64:visit_i64);
438+
}
439+
440+
deserializer.$deserialize(SaturatingVisitor)
441+
}
442+
}
443+
};
444+
}
445+
446+
impl_deserialize_saturating_num!(u8, deserialize_u8);
447+
impl_deserialize_saturating_num!(u16, deserialize_u16);
448+
impl_deserialize_saturating_num!(u32, deserialize_u32);
449+
impl_deserialize_saturating_num!(u64, deserialize_u64);
450+
impl_deserialize_saturating_num!(usize, deserialize_u64);
451+
impl_deserialize_saturating_num!(i8, deserialize_i8);
452+
impl_deserialize_saturating_num!(i16, deserialize_i16);
453+
impl_deserialize_saturating_num!(i32, deserialize_i32);
454+
impl_deserialize_saturating_num!(i64, deserialize_i64);
455+
impl_deserialize_saturating_num!(isize, deserialize_i64);
456+
390457
macro_rules! num_128 {
391458
($ty:ident : $visit:ident) => {
392459
fn $visit<E>(self, v: $ty) -> Result<Self::Value, E>

serde/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ mod lib {
274274
pub use std::sync::atomic::{AtomicI64, AtomicU64};
275275
#[cfg(all(feature = "std", not(no_target_has_atomic), target_has_atomic = "ptr"))]
276276
pub use std::sync::atomic::{AtomicIsize, AtomicUsize};
277+
278+
#[cfg(not(no_core_num_saturating))]
279+
pub use self::core::num::Saturating;
277280
}
278281

279282
// None of this crate's error handling needs the `From::from` error conversion

serde/src/ser/impls.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,20 @@ where
10261026
}
10271027
}
10281028

1029+
#[cfg(not(no_core_num_saturating))]
1030+
impl<T> Serialize for Saturating<T>
1031+
where
1032+
T: Serialize,
1033+
{
1034+
#[inline]
1035+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1036+
where
1037+
S: Serializer,
1038+
{
1039+
self.0.serialize(serializer)
1040+
}
1041+
}
1042+
10291043
impl<T> Serialize for Reverse<T>
10301044
where
10311045
T: Serialize,

test_suite/tests/test_de.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::iter;
2323
use std::net;
2424
use std::num::{
2525
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
26-
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
26+
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Saturating, Wrapping,
2727
};
2828
use std::ops::Bound;
2929
use std::path::{Path, PathBuf};
@@ -2065,6 +2065,43 @@ fn test_wrapping() {
20652065
test(Wrapping(1usize), &[Token::U64(1)]);
20662066
}
20672067

2068+
#[test]
2069+
fn test_saturating() {
2070+
test(Saturating(1usize), &[Token::U32(1)]);
2071+
test(Saturating(1usize), &[Token::U64(1)]);
2072+
test(Saturating(0u8), &[Token::I8(0)]);
2073+
test(Saturating(0u16), &[Token::I16(0)]);
2074+
2075+
// saturate input values at the minimum or maximum value
2076+
test(Saturating(u8::MAX), &[Token::U16(u16::MAX)]);
2077+
test(Saturating(u8::MAX), &[Token::U16(u8::MAX as u16 + 1)]);
2078+
test(Saturating(u16::MAX), &[Token::U32(u32::MAX)]);
2079+
test(Saturating(u32::MAX), &[Token::U64(u64::MAX)]);
2080+
test(Saturating(u8::MIN), &[Token::I8(i8::MIN)]);
2081+
test(Saturating(u16::MIN), &[Token::I16(i16::MIN)]);
2082+
test(Saturating(u32::MIN), &[Token::I32(i32::MIN)]);
2083+
test(Saturating(i8::MIN), &[Token::I16(i16::MIN)]);
2084+
test(Saturating(i16::MIN), &[Token::I32(i32::MIN)]);
2085+
test(Saturating(i32::MIN), &[Token::I64(i64::MIN)]);
2086+
2087+
test(Saturating(u8::MIN), &[Token::I8(-1)]);
2088+
test(Saturating(u16::MIN), &[Token::I16(-1)]);
2089+
2090+
#[cfg(target_pointer_width = "64")]
2091+
{
2092+
test(Saturating(usize::MIN), &[Token::U64(u64::MIN)]);
2093+
test(Saturating(usize::MAX), &[Token::U64(u64::MAX)]);
2094+
test(Saturating(isize::MIN), &[Token::I64(i64::MIN)]);
2095+
test(Saturating(isize::MAX), &[Token::I64(i64::MAX)]);
2096+
test(Saturating(0usize), &[Token::I64(i64::MIN)]);
2097+
2098+
test(
2099+
Saturating(9_223_372_036_854_775_807usize),
2100+
&[Token::I64(i64::MAX)],
2101+
);
2102+
}
2103+
}
2104+
20682105
#[test]
20692106
fn test_rc_dst() {
20702107
test(Rc::<str>::from("s"), &[Token::Str("s")]);

test_suite/tests/test_ser.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::cell::RefCell;
88
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
99
use std::ffi::CString;
1010
use std::net;
11-
use std::num::Wrapping;
11+
use std::num::{Saturating, Wrapping};
1212
use std::ops::Bound;
1313
use std::path::{Path, PathBuf};
1414
use std::rc::{Rc, Weak as RcWeak};
@@ -624,6 +624,11 @@ fn test_wrapping() {
624624
assert_ser_tokens(&Wrapping(1usize), &[Token::U64(1)]);
625625
}
626626

627+
#[test]
628+
fn test_saturating() {
629+
assert_ser_tokens(&Saturating(1usize), &[Token::U64(1)]);
630+
}
631+
627632
#[test]
628633
fn test_rc_dst() {
629634
assert_ser_tokens(&Rc::<str>::from("s"), &[Token::Str("s")]);

0 commit comments

Comments
 (0)