From 4bb45c82520da958ce89241e3dcd3a6b23fcac96 Mon Sep 17 00:00:00 2001 From: Dmitry Shlagoff Date: Tue, 29 Jan 2019 15:20:27 +0700 Subject: [PATCH 1/3] Impl Serialize for Bound --- serde/src/de/impls.rs | 110 +++++++++++++++++++++++++++++++++++ serde/src/lib.rs | 2 +- serde/src/ser/impls.rs | 18 ++++++ test_suite/tests/test_de.rs | 18 ++++++ test_suite/tests/test_ser.rs | 1 + 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/serde/src/de/impls.rs b/serde/src/de/impls.rs index 95531661..29148702 100644 --- a/serde/src/de/impls.rs +++ b/serde/src/de/impls.rs @@ -2269,6 +2269,116 @@ mod range { //////////////////////////////////////////////////////////////////////////////// +impl<'de, T> Deserialize<'de> for Bound +where + T: Deserialize<'de> +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + enum Field { + Unbounded, + Included, + Excluded, + } + + impl<'de> Deserialize<'de> for Field { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("`Unbounded`, `Included` or `Excluded`") + } + + fn visit_u32(self, value: u32) -> Result + where + E: Error, + { + match value { + 0 => Ok(Field::Unbounded), + 1 => Ok(Field::Included), + 2 => Ok(Field::Excluded), + _ => Err(Error::invalid_value( + Unexpected::Unsigned(value as u64), + &self, + )), + } + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + match value { + "Unbounded" => Ok(Field::Unbounded), + "Included" => Ok(Field::Included), + "Excluded" => Ok(Field::Excluded), + _ => Err(Error::unknown_variant(value, VARIANTS)), + } + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: Error, + { + match value { + b"Unbounded" => Ok(Field::Unbounded), + b"Included" => Ok(Field::Included), + b"Excluded" => Ok(Field::Excluded), + _ => match str::from_utf8(value) { + Ok(value) => Err(Error::unknown_variant(value, VARIANTS)), + Err(_) => { + Err(Error::invalid_value(Unexpected::Bytes(value), &self)) + } + }, + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct BoundVisitor(PhantomData>); + + impl<'de, T> Visitor<'de> for BoundVisitor + where + T: Deserialize<'de>, + { + type Value = Bound; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("enum Bound") + } + + fn visit_enum(self, data: A) -> Result + where + A: EnumAccess<'de>, + { + match try!(data.variant()) { + (Field::Unbounded, v) => v.newtype_variant().map(|_: T| Bound::Unbounded), + (Field::Included, v) => v.newtype_variant().map(Bound::Included), + (Field::Excluded, v) => v.newtype_variant().map(Bound::Excluded), + } + } + } + + const VARIANTS: &'static [&'static str] = &["Unbounded", "Included", "Excluded"]; + + deserializer.deserialize_enum("Bound", VARIANTS, BoundVisitor(PhantomData)) + } +} + +//////////////////////////////////////////////////////////////////////////////// + macro_rules! nonzero_integers { ( $( $T: ident, )+ ) => { $( diff --git a/serde/src/lib.rs b/serde/src/lib.rs index 9074899b..c2fe3ff2 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -160,7 +160,7 @@ mod lib { pub use self::core::default::{self, Default}; pub use self::core::fmt::{self, Debug, Display}; pub use self::core::marker::{self, PhantomData}; - pub use self::core::ops::Range; + pub use self::core::ops::{Range, Bound}; pub use self::core::option::{self, Option}; pub use self::core::result::{self, Result}; diff --git a/serde/src/ser/impls.rs b/serde/src/ser/impls.rs index 3fab473d..af672418 100644 --- a/serde/src/ser/impls.rs +++ b/serde/src/ser/impls.rs @@ -256,6 +256,24 @@ where //////////////////////////////////////////////////////////////////////////////// +impl Serialize for Bound +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + Bound::Unbounded => serializer.serialize_unit_variant("Bound", 0, "Unbounded"), + Bound::Included(ref value) => serializer.serialize_newtype_variant("Bound", 1, "Included", value), + Bound::Excluded(ref value) => serializer.serialize_newtype_variant("Bound", 2, "Excluded", value), + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + impl Serialize for () { #[inline] fn serialize(&self, serializer: S) -> Result diff --git a/test_suite/tests/test_de.rs b/test_suite/tests/test_de.rs index 5836d547..461494f3 100644 --- a/test_suite/tests/test_de.rs +++ b/test_suite/tests/test_de.rs @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf}; use std::rc::{Rc, Weak as RcWeak}; use std::sync::{Arc, Weak as ArcWeak}; use std::time::{Duration, UNIX_EPOCH}; +use std::ops::Bound; use fnv::FnvHasher; use serde::{Deserialize, Deserializer}; @@ -836,6 +837,23 @@ declare_tests! { Token::SeqEnd, ], } + test_bound { + Bound::Unbounded::<()> => &[ + Token::Enum { name: "Bound" }, + Token::Str("Unbounded"), + Token::Unit, + ], + Bound::Included(0) => &[ + Token::Enum { name: "Bound" }, + Token::Str("Included"), + Token::U8(0), + ], + Bound::Excluded(0) => &[ + Token::Enum { name: "Bound" }, + Token::Str("Excluded"), + Token::U8(0), + ], + } test_path { Path::new("/usr/local/lib") => &[ Token::BorrowedStr("/usr/local/lib"), diff --git a/test_suite/tests/test_ser.rs b/test_suite/tests/test_ser.rs index b7bde4e1..f96961ab 100644 --- a/test_suite/tests/test_ser.rs +++ b/test_suite/tests/test_ser.rs @@ -10,6 +10,7 @@ use std::path::{Path, PathBuf}; use std::rc::{Rc, Weak as RcWeak}; use std::sync::{Arc, Weak as ArcWeak}; use std::time::{Duration, UNIX_EPOCH}; +use std::ops::Bound; #[cfg(unix)] use std::str; From 0def7da5a8fbb2b31b4caada650e112ec73685fb Mon Sep 17 00:00:00 2001 From: Dmitry Shlagoff Date: Tue, 29 Jan 2019 20:29:14 +0700 Subject: [PATCH 2/3] Impl Ser/De for RangeFrom, RangeTo, RangeToInclusive --- serde/src/de/impls.rs | 144 ++++++++++++++++++++++++++++++++++- serde/src/lib.rs | 2 +- serde/src/ser/impls.rs | 51 +++++++++++++ test_suite/tests/test_de.rs | 24 ++++++ test_suite/tests/test_ser.rs | 41 ++++++++++ 5 files changed, 260 insertions(+), 2 deletions(-) diff --git a/serde/src/de/impls.rs b/serde/src/de/impls.rs index 29148702..9da2b918 100644 --- a/serde/src/de/impls.rs +++ b/serde/src/de/impls.rs @@ -2138,6 +2138,72 @@ where } } +impl<'de, Idx> Deserialize<'de> for RangeFrom +where + Idx: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let field = range::Field::Start; + let start = deserializer.deserialize_struct( + "RangeFrom", + field.name_slice(), + range::UnboundedRangeVisitor { + expecting: "struct RangeFrom", + phantom: PhantomData, + field, + }, + )?; + Ok(start..) + } +} + +impl<'de, Idx> Deserialize<'de> for RangeTo +where + Idx: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let field = range::Field::End; + let end = deserializer.deserialize_struct( + "RangeTo", + field.name_slice(), + range::UnboundedRangeVisitor { + expecting: "struct RangeTo", + phantom: PhantomData, + field, + }, + )?; + Ok(..end) + } +} + +impl<'de, Idx> Deserialize<'de> for RangeToInclusive +where + Idx: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let field = range::Field::End; + let end = deserializer.deserialize_struct( + "RangeToInclusive", + field.name_slice(), + range::UnboundedRangeVisitor { + expecting: "struct RangeToInclusive", + phantom: PhantomData, + field, + }, + )?; + Ok(..=end) + } +} + mod range { use lib::*; @@ -2149,11 +2215,31 @@ mod range { // // #[derive(Deserialize)] // #[serde(field_identifier, rename_all = "lowercase")] - enum Field { + #[derive(PartialEq)] + pub enum Field { Start, End, } + const FIELDS_START_ONLY: &'static [&'static str] = &["start"]; + const FIELD_END_ONLY: &'static [&'static str] = &["end"]; + + impl Field { + fn name(&self) -> &'static str { + match self { + Field::Start => "start", + Field::End => "end", + } + } + + pub fn name_slice(&self) -> &'static [&'static str] { + match self { + Field::Start => FIELDS_START_ONLY, + Field::End => FIELD_END_ONLY, + } + } + } + impl<'de> Deserialize<'de> for Field { fn deserialize(deserializer: D) -> Result where @@ -2265,6 +2351,62 @@ mod range { Ok((start, end)) } } + + pub struct UnboundedRangeVisitor { + pub expecting: &'static str, + pub phantom: PhantomData, + pub field: Field, + } + + impl<'de, Idx> Visitor<'de> for UnboundedRangeVisitor + where + Idx: Deserialize<'de>, + { + type Value = Idx; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.expecting) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let value: Idx = match try!(seq.next_element()) { + Some(value) => value, + None => { + return Err(Error::invalid_length(0, &self)); + } + }; + Ok(value) + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut value: Option = None; + while let Some(key) = try!(map.next_key()) { + let key: Field = key; + match key { + ref key if *key == self.field => { + if value.is_some() { + return Err(::duplicate_field(key.name())); + } + value = Some(try!(map.next_value())); + } + key => { + return Err(::unknown_field(key.name(), self.field.name_slice())); + } + } + } + let value = match value { + Some(value) => value, + None => return Err(::missing_field(self.field.name())), + }; + Ok(value) + } + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/serde/src/lib.rs b/serde/src/lib.rs index c2fe3ff2..07202fa6 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -160,7 +160,7 @@ mod lib { pub use self::core::default::{self, Default}; pub use self::core::fmt::{self, Debug, Display}; pub use self::core::marker::{self, PhantomData}; - pub use self::core::ops::{Range, Bound}; + pub use self::core::ops::{Range, Bound, RangeFrom, RangeTo, RangeToInclusive}; pub use self::core::option::{self, Option}; pub use self::core::result::{self, Result}; diff --git a/serde/src/ser/impls.rs b/serde/src/ser/impls.rs index af672418..c290889b 100644 --- a/serde/src/ser/impls.rs +++ b/serde/src/ser/impls.rs @@ -256,6 +256,57 @@ where //////////////////////////////////////////////////////////////////////////////// +impl Serialize for RangeFrom +where + Idx: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use super::SerializeStruct; + let mut state = try!(serializer.serialize_struct("RangeFrom", 1)); + try!(state.serialize_field("start", &self.start)); + state.end() + } +} + +//////////////////////////////////////////////////////////////////////////////// + +impl Serialize for RangeTo +where + Idx: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use super::SerializeStruct; + let mut state = try!(serializer.serialize_struct("RangeTo", 1)); + try!(state.serialize_field("end", &self.end)); + state.end() + } +} + +//////////////////////////////////////////////////////////////////////////////// + +impl Serialize for RangeToInclusive +where + Idx: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use super::SerializeStruct; + let mut state = try!(serializer.serialize_struct("RangeToInclusive", 1)); + try!(state.serialize_field("end", &self.end)); + state.end() + } +} + +//////////////////////////////////////////////////////////////////////////////// + impl Serialize for Bound where T: Serialize, diff --git a/test_suite/tests/test_de.rs b/test_suite/tests/test_de.rs index 461494f3..8a4b346d 100644 --- a/test_suite/tests/test_de.rs +++ b/test_suite/tests/test_de.rs @@ -837,6 +837,30 @@ declare_tests! { Token::SeqEnd, ], } + test_range_from { + 0u8.. => &[ + Token::Struct { name: "RangeFrom", len: 1 }, + Token::Str("start"), + Token::U8(0), + Token::StructEnd, + ], + } + test_range_to { + ..2u8 => &[ + Token::Struct { name: "RangeTo", len: 1 }, + Token::Str("end"), + Token::U8(2), + Token::StructEnd, + ], + } + test_range_to_inclusive { + ..=2u8 => &[ + Token::Struct { name: "RangeToInclusive", len: 1 }, + Token::Str("end"), + Token::U8(2), + Token::StructEnd, + ], + } test_bound { Bound::Unbounded::<()> => &[ Token::Enum { name: "Bound" }, diff --git a/test_suite/tests/test_ser.rs b/test_suite/tests/test_ser.rs index f96961ab..9a56e9eb 100644 --- a/test_suite/tests/test_ser.rs +++ b/test_suite/tests/test_ser.rs @@ -376,6 +376,47 @@ declare_tests! { Token::StructEnd, ], } + test_range_from { + 0u8.. => &[ + Token::Struct { name: "RangeFrom", len: 1 }, + Token::Str("start"), + Token::U8(0), + Token::StructEnd, + ], + } + test_range_to { + ..2u8 => &[ + Token::Struct { name: "RangeTo", len: 1 }, + Token::Str("end"), + Token::U8(2), + Token::StructEnd, + ], + } + test_range_to_inclusive { + ..=2u8 => &[ + Token::Struct { name: "RangeToInclusive", len: 1 }, + Token::Str("end"), + Token::U8(2), + Token::StructEnd, + ], + } + test_bound { + Bound::Unbounded::<()> => &[ + Token::Enum { name: "Bound" }, + Token::Str("Unbounded"), + Token::Unit, + ], + Bound::Included(0u8) => &[ + Token::Enum { name: "Bound" }, + Token::Str("Included"), + Token::U8(0), + ], + Bound::Excluded(0u8) => &[ + Token::Enum { name: "Bound" }, + Token::Str("Excluded"), + Token::U8(0), + ], + } test_path { Path::new("/usr/local/lib") => &[ Token::Str("/usr/local/lib"), From 18b1604fc8598a1f8adcadb2fc0f832b584be24c Mon Sep 17 00:00:00 2001 From: Dmitry Shlagoff Date: Tue, 29 Jan 2019 21:32:41 +0700 Subject: [PATCH 3/3] Fix compatibility issues with syntax and Bound --- serde/build.rs | 15 +++++++++++++++ serde/src/de/impls.rs | 14 ++++++++------ serde/src/lib.rs | 11 ++++++++++- serde/src/ser/impls.rs | 3 ++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/serde/build.rs b/serde/build.rs index c616d181..453514c7 100644 --- a/serde/build.rs +++ b/serde/build.rs @@ -14,6 +14,15 @@ fn main() { let target = env::var("TARGET").unwrap(); let emscripten = target == "asmjs-unknown-emscripten" || target == "wasm32-unknown-emscripten"; + // std::collections::Bound was stabilized in Rust 1.17 + // but it was moved to core::ops later in Rust 1.26: + // https://doc.rust-lang.org/core/ops/enum.Bound.html + if minor >= 26 { + println!("cargo:rustc-cfg=ops_bound"); + } else if minor >= 17 && cfg!(feature = "std") { + println!("cargo:rustc-cfg=collections_bound"); + } + // CString::into_boxed_c_str stabilized in Rust 1.20: // https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_boxed_c_str if minor >= 20 { @@ -42,6 +51,12 @@ fn main() { println!("cargo:rustc-cfg=integer128"); } + // RangeToInclusive was stabilized in Rust 1.26: + // https://doc.rust-lang.org/std/ops/struct.RangeToInclusive.html + if minor >= 26 { + println!("cargo:rustc-cfg=range_to_inclusive"); + } + // Inclusive ranges methods stabilized in Rust 1.27: // https://github.com/rust-lang/rust/pull/50758 if minor >= 27 { diff --git a/serde/src/de/impls.rs b/serde/src/de/impls.rs index 9da2b918..57d8307e 100644 --- a/serde/src/de/impls.rs +++ b/serde/src/de/impls.rs @@ -2153,7 +2153,7 @@ where range::UnboundedRangeVisitor { expecting: "struct RangeFrom", phantom: PhantomData, - field, + field: field, }, )?; Ok(start..) @@ -2175,13 +2175,14 @@ where range::UnboundedRangeVisitor { expecting: "struct RangeTo", phantom: PhantomData, - field, + field: field, }, )?; Ok(..end) } } +#[cfg(range_to_inclusive)] impl<'de, Idx> Deserialize<'de> for RangeToInclusive where Idx: Deserialize<'de>, @@ -2197,10 +2198,10 @@ where range::UnboundedRangeVisitor { expecting: "struct RangeToInclusive", phantom: PhantomData, - field, + field: field, }, )?; - Ok(..=end) + Ok(RangeToInclusive { end: end }) } } @@ -2226,14 +2227,14 @@ mod range { impl Field { fn name(&self) -> &'static str { - match self { + match *self { Field::Start => "start", Field::End => "end", } } pub fn name_slice(&self) -> &'static [&'static str] { - match self { + match *self { Field::Start => FIELDS_START_ONLY, Field::End => FIELD_END_ONLY, } @@ -2411,6 +2412,7 @@ mod range { //////////////////////////////////////////////////////////////////////////////// +#[cfg(any(ops_bound, collections_bound))] impl<'de, T> Deserialize<'de> for Bound where T: Deserialize<'de> diff --git a/serde/src/lib.rs b/serde/src/lib.rs index 07202fa6..f242ca57 100644 --- a/serde/src/lib.rs +++ b/serde/src/lib.rs @@ -160,7 +160,7 @@ mod lib { pub use self::core::default::{self, Default}; pub use self::core::fmt::{self, Debug, Display}; pub use self::core::marker::{self, PhantomData}; - pub use self::core::ops::{Range, Bound, RangeFrom, RangeTo, RangeToInclusive}; + pub use self::core::ops::{Range, RangeFrom, RangeTo}; pub use self::core::option::{self, Option}; pub use self::core::result::{self, Result}; @@ -224,6 +224,15 @@ mod lib { #[cfg(range_inclusive)] pub use self::core::ops::RangeInclusive; + + #[cfg(all(feature = "std", collections_bound))] + pub use std::collections::Bound; + + #[cfg(ops_bound)] + pub use self::core::ops::Bound; + + #[cfg(range_to_inclusive)] + pub use self::core::ops::RangeToInclusive; } //////////////////////////////////////////////////////////////////////////////// diff --git a/serde/src/ser/impls.rs b/serde/src/ser/impls.rs index c290889b..0adfd535 100644 --- a/serde/src/ser/impls.rs +++ b/serde/src/ser/impls.rs @@ -289,7 +289,7 @@ where } //////////////////////////////////////////////////////////////////////////////// - +#[cfg(range_to_inclusive)] impl Serialize for RangeToInclusive where Idx: Serialize, @@ -307,6 +307,7 @@ where //////////////////////////////////////////////////////////////////////////////// +#[cfg(any(ops_bound, collections_bound))] impl Serialize for Bound where T: Serialize,