From d3bb9920e41bdb3ea60140c75f18231ff8450cde Mon Sep 17 00:00:00 2001 From: Kirill Semyonkin Date: Thu, 10 Aug 2023 12:51:31 +0300 Subject: [PATCH] Add impl for PartialOrd, Ord, reverse PartialEq (#27) This PR implements `PartialOrd` and `Ord` for `IString`, fixes #18, additionally adds more implementations for reverse comparisons (`str = IString`, as opposed to usual `IString = str`). It adds an `impl_cmp_as_str!` macro which allows to list implementations easily by mentioning the trait name and pairs for which to implement the trait. It also updates tests using macros to list more possible usage variants (comparing `IString`s of different `enum` variants for all tests, reverse comparisons, adding tests for `PartialOrd` and `Ord`). --- src/string.rs | 244 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 202 insertions(+), 42 deletions(-) diff --git a/src/string.rs b/src/string.rs index 82d37d7..38548bd 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::cmp::Ordering; use std::fmt::Debug; use std::str::FromStr; @@ -6,7 +7,7 @@ use std::str::FromStr; /// /// This type is cheap to clone and thus implements [`ImplicitClone`]. It can be created based on a /// `&'static str` or based on a reference counted string slice ([`str`]). -#[derive(Debug, Clone, Eq)] +#[derive(Debug, Clone)] pub enum IString { /// A static string slice. Static(&'static str), @@ -91,35 +92,49 @@ impl From> for IString { } } -impl PartialEq for IString { - fn eq(&self, other: &IString) -> bool { - self.as_str().eq(other.as_str()) - } -} - -impl PartialEq for IString { - fn eq(&self, other: &str) -> bool { - self.as_str().eq(other) - } -} - -impl PartialEq<&str> for IString { - fn eq(&self, other: &&str) -> bool { - self.eq(*other) - } +macro_rules! impl_cmp_as_str { + (PartialEq::<$type1:ty, $type2:ty>) => { + impl_cmp_as_str!(PartialEq::<$type1, $type2>::eq -> bool); + }; + (PartialOrd::<$type1:ty, $type2:ty>) => { + impl_cmp_as_str!(PartialOrd::<$type1, $type2>::partial_cmp -> Option); + }; + ($trait:ident :: <$type1:ty, $type2:ty> :: $fn:ident -> $ret:ty) => { + impl $trait<$type2> for $type1 { + fn $fn(&self, other: &$type2) -> $ret { + $trait::$fn(AsRef::::as_ref(self), AsRef::::as_ref(other)) + } + } + }; } -impl PartialEq for IString { - fn eq(&self, other: &String) -> bool { - self.eq(other.as_str()) +impl Eq for IString {} + +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::<&str, IString>); +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::); +impl_cmp_as_str!(PartialEq::<&String, IString>); + +impl Ord for IString { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + Ord::cmp(AsRef::::as_ref(self), AsRef::::as_ref(other)) } } -impl PartialEq<&String> for IString { - fn eq(&self, other: &&String) -> bool { - self.eq(*other) - } -} +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::<&str, IString>); +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::); +impl_cmp_as_str!(PartialOrd::<&String, IString>); impl std::ops::Deref for IString { type Target = str; @@ -163,7 +178,7 @@ impl serde::Serialize for IString { } #[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for IString{ +impl<'de> serde::Deserialize<'de> for IString { fn deserialize>(deserializer: D) -> Result { ::deserialize(deserializer).map(IString::from) } @@ -173,40 +188,183 @@ impl<'de> serde::Deserialize<'de> for IString{ mod test_string { use super::*; + // + // Frames wrap a value with a particular syntax + // that may not be easy to write with plain macro_rules arg types + // + + macro_rules! frame_i_static { + ($a:expr) => { + IString::Static($a) + }; + } + + macro_rules! frame_i_rc { + ($a:expr) => { + IString::Rc(Rc::from($a)) + }; + } + + macro_rules! frame_deref { + ($a:expr) => { + *$a + }; + } + + macro_rules! frame_noop { + ($a:expr) => { + $a + }; + } + + macro_rules! frame_string { + ($a:expr) => { + String::from($a) + }; + } + + macro_rules! frame_string_ref { + ($a:expr) => { + &String::from($a) + }; + } + + #[test] + fn eq_ne_self() { + macro_rules! test_one { + ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:expr, $b:expr) => { + $macro!($frame1!($a), $frame2!($b)); + }; + } + + macro_rules! test_all_frame_combos { + ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => { + // 11, 12, 21, 22 - i_static-i_static, i_static-i_rc, ... + test_one!($macro!, $frame1!, $frame1!, $a, $b); + test_one!($macro!, $frame2!, $frame1!, $a, $b); + test_one!($macro!, $frame1!, $frame2!, $a, $b); + test_one!($macro!, $frame2!, $frame2!, $a, $b); + }; + ($macro:tt!, $a:literal, $b:literal) => { + test_all_frame_combos!($macro!, frame_i_static!, frame_i_rc!, $a, $b); + }; + } + + test_all_frame_combos!(assert_eq!, "foo", "foo"); + test_all_frame_combos!(assert_ne!, "foo", "bar"); + } + + #[test] + fn cmp_self() { + macro_rules! test_one { + ($res:expr, $frame1:tt!, $frame2:tt!, $a:expr, $b:expr) => { + assert_eq!($res, Ord::cmp(&$frame1!($a), &$frame2!($b))); + }; + } + + macro_rules! test_all_frame_combos { + ($res:expr, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => { + // 11, 12, 21, 22 - i_static-i_static, i_static-i_rc, ... + test_one!($res, $frame1!, $frame1!, $a, $b); + test_one!($res, $frame2!, $frame1!, $a, $b); + test_one!($res, $frame1!, $frame2!, $a, $b); + test_one!($res, $frame2!, $frame2!, $a, $b); + }; + ($res:expr, $a:literal, $b:literal) => { + test_all_frame_combos!($res, frame_i_static!, frame_i_rc!, $a, $b); + }; + } + + test_all_frame_combos!(Ordering::Equal, "foo", "foo"); + test_all_frame_combos!(Ordering::Greater, "foo", "bar"); + test_all_frame_combos!(Ordering::Less, "bar", "foo"); + test_all_frame_combos!(Ordering::Greater, "foobar", "foo"); + test_all_frame_combos!(Ordering::Less, "foo", "foobar"); + } + #[test] - fn cmp() { - assert_eq!(IString::Static("foo"), IString::Static("foo")); - assert_eq!(IString::Static("foo"), IString::Rc(Rc::from("foo"))); - assert_eq!(IString::Rc(Rc::from("foo")), IString::Rc(Rc::from("foo"))); + fn eq_ne_strings() { + macro_rules! test_one { + ($macro:tt!, $a:expr, $b:expr) => { + $macro!($a, $b); + $macro!($b, $a); + }; + } + + macro_rules! test_all_frame_combos { + ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => { + // 12, 21 - i_rc-deref, deref-i_rc, ..., static-string_ref, string_ref-static + test_one!($macro!, $frame1!($a), $frame2!($b)); + test_one!($macro!, $frame2!($a), $frame1!($b)); + }; + ($macro:tt!, $frame2:tt!, $a:literal, $b:literal) => { + test_all_frame_combos!($macro!, frame_i_rc!, $frame2!, $a, $b); + test_all_frame_combos!($macro!, frame_i_static!, $frame2!, $a, $b); + }; + ($macro:tt!, $a:literal, $b:literal) => { + test_all_frame_combos!($macro!, frame_deref!, $a, $b); + test_all_frame_combos!($macro!, frame_noop!, $a, $b); + test_all_frame_combos!($macro!, frame_string!, $a, $b); + test_all_frame_combos!($macro!, frame_string_ref!, $a, $b); + }; + } - assert_ne!(IString::Static("foo"), IString::Static("bar")); - assert_ne!(IString::Static("foo"), IString::Rc(Rc::from("bar"))); - assert_ne!(IString::Rc(Rc::from("foo")), IString::Rc(Rc::from("bar"))); + test_all_frame_combos!(assert_eq!, "foo", "foo"); + test_all_frame_combos!(assert_ne!, "foo", "bar"); } #[test] - fn string_cmp() { - assert_eq!(IString::from("foo"), "foo"); - assert_eq!(IString::from("foo"), String::from("foo")); - assert_eq!(IString::from("foo"), &String::from("foo")); + fn partial_cmp_strings() { + macro_rules! test_one { + ($res:expr, $a:expr, $b:expr) => { + assert_eq!(Some($res), PartialOrd::partial_cmp(&$a, &$b)); + }; + } + + macro_rules! test_all_frame_combos { + ($res:expr, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => { + // 12, 21 - i_rc-deref, deref-i_rc, ..., static-string_ref, string_ref-static + test_one!($res, $frame1!($a), $frame2!($b)); + test_one!($res, $frame2!($a), $frame1!($b)); + }; + ($res:expr, $frame2:tt!, $a:literal, $b:literal) => { + test_all_frame_combos!($res, frame_i_rc!, $frame2!, $a, $b); + test_all_frame_combos!($res, frame_i_static!, $frame2!, $a, $b); + }; + ($res:expr, $a:literal, $b:literal) => { + test_all_frame_combos!($res, frame_deref!, $a, $b); + test_all_frame_combos!($res, frame_noop!, $a, $b); + test_all_frame_combos!($res, frame_string!, $a, $b); + test_all_frame_combos!($res, frame_string_ref!, $a, $b); + }; + } + + test_all_frame_combos!(Ordering::Equal, "foo", "foo"); + test_all_frame_combos!(Ordering::Greater, "foo", "bar"); + test_all_frame_combos!(Ordering::Less, "bar", "foo"); + test_all_frame_combos!(Ordering::Greater, "foobar", "foo"); + test_all_frame_combos!(Ordering::Less, "foo", "foobar"); } #[test] - fn static_string() { + fn const_string() { const _STRING: IString = IString::Static("foo"); } #[test] fn deref_str() { assert_eq!(IString::Static("foo").to_uppercase(), "FOO"); + assert_eq!(IString::Rc(Rc::from("foo")).to_uppercase(), "FOO"); } #[test] fn borrow_str() { let map: std::collections::HashMap<_, _> = [ (IString::Static("foo"), true), - (IString::Rc(Rc::from("bar")), true) - ].into_iter().collect(); + (IString::Rc(Rc::from("bar")), true), + ] + .into_iter() + .collect(); assert_eq!(map.get("foo").copied(), Some(true)); assert_eq!(map.get("bar").copied(), Some(true)); @@ -215,12 +373,14 @@ mod test_string { #[test] fn as_cow_does_not_clone() { let rc_s = Rc::from("foo"); + let s = IString::Rc(Rc::clone(&rc_s)); assert_eq!(Rc::strong_count(&rc_s), 2); + let cow: Cow<'_, str> = s.as_cow(); assert_eq!(Rc::strong_count(&rc_s), 2); - // this assert exists to ensure the cow lives after the strong_count asset - assert_eq!(cow, "foo"); + // this assert exists to ensure the cow lives after the strong_count assert + assert_eq!(cow, "foo"); } }