1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::{self, Debug};
4use std::str::FromStr;
5
6use crate::ImplicitClone;
7
8use super::Rc;
9
10#[derive(Debug, Clone)]
15pub enum IString {
16 Static(&'static str),
18 Rc(Rc<str>),
20}
21
22impl IString {
23 pub fn as_str(&self) -> &str {
36 match self {
37 Self::Static(s) => s,
38 Self::Rc(s) => s,
39 }
40 }
41
42 pub fn as_cow(&self) -> Cow<'_, str> {
54 Cow::Borrowed(self.as_str())
55 }
56}
57
58impl Default for IString {
59 fn default() -> Self {
60 Self::Static("")
61 }
62}
63
64impl ImplicitClone for IString {}
65
66impl fmt::Display for IString {
67 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68 fmt::Display::fmt(self.as_str(), f)
69 }
70}
71
72impl From<&'static str> for IString {
73 fn from(s: &'static str) -> IString {
74 IString::Static(s)
75 }
76}
77
78impl From<String> for IString {
79 fn from(s: String) -> IString {
80 IString::Rc(Rc::from(s))
81 }
82}
83
84impl From<Rc<str>> for IString {
85 fn from(s: Rc<str>) -> IString {
86 IString::Rc(s)
87 }
88}
89
90impl From<Cow<'static, str>> for IString {
91 fn from(cow: Cow<'static, str>) -> Self {
92 match cow {
93 Cow::Borrowed(s) => IString::Static(s),
94 Cow::Owned(s) => s.into(),
95 }
96 }
97}
98
99impl From<&IString> for IString {
100 fn from(s: &IString) -> IString {
101 s.clone()
102 }
103}
104
105macro_rules! impl_cmp_as_str {
106 (PartialEq::<$type1:ty, $type2:ty>) => {
107 impl_cmp_as_str!(PartialEq::<$type1, $type2>::eq -> bool);
108 };
109 (PartialOrd::<$type1:ty, $type2:ty>) => {
110 impl_cmp_as_str!(PartialOrd::<$type1, $type2>::partial_cmp -> Option<Ordering>);
111 };
112 ($trait:ident :: <$type1:ty, $type2:ty> :: $fn:ident -> $ret:ty) => {
113 impl $trait<$type2> for $type1 {
114 fn $fn(&self, other: &$type2) -> $ret {
115 $trait::$fn(AsRef::<str>::as_ref(self), AsRef::<str>::as_ref(other))
116 }
117 }
118 };
119}
120
121impl Eq for IString {}
122
123impl_cmp_as_str!(PartialEq::<IString, IString>);
124impl_cmp_as_str!(PartialEq::<IString, str>);
125impl_cmp_as_str!(PartialEq::<str, IString>);
126impl_cmp_as_str!(PartialEq::<IString, &str>);
127impl_cmp_as_str!(PartialEq::<&str, IString>);
128impl_cmp_as_str!(PartialEq::<IString, String>);
129impl_cmp_as_str!(PartialEq::<String, IString>);
130impl_cmp_as_str!(PartialEq::<IString, &String>);
131impl_cmp_as_str!(PartialEq::<&String, IString>);
132
133impl Ord for IString {
134 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
135 Ord::cmp(AsRef::<str>::as_ref(self), AsRef::<str>::as_ref(other))
136 }
137}
138
139impl PartialOrd for IString {
142 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
143 Some(self.cmp(other))
144 }
145}
146
147impl_cmp_as_str!(PartialOrd::<IString, str>);
148impl_cmp_as_str!(PartialOrd::<str, IString>);
149impl_cmp_as_str!(PartialOrd::<IString, &str>);
150impl_cmp_as_str!(PartialOrd::<&str, IString>);
151impl_cmp_as_str!(PartialOrd::<IString, String>);
152impl_cmp_as_str!(PartialOrd::<String, IString>);
153impl_cmp_as_str!(PartialOrd::<IString, &String>);
154impl_cmp_as_str!(PartialOrd::<&String, IString>);
155
156impl std::ops::Deref for IString {
157 type Target = str;
158
159 fn deref(&self) -> &Self::Target {
160 self.as_str()
161 }
162}
163
164impl AsRef<str> for IString {
165 fn as_ref(&self) -> &str {
166 self.as_str()
167 }
168}
169
170impl std::hash::Hash for IString {
171 #[inline]
172 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
173 std::hash::Hash::hash(self.as_str(), state)
174 }
175}
176
177impl std::borrow::Borrow<str> for IString {
178 fn borrow(&self) -> &str {
179 self.as_str()
180 }
181}
182
183impl FromStr for IString {
184 type Err = std::convert::Infallible;
185 fn from_str(value: &str) -> Result<Self, Self::Err> {
186 Ok(IString::from(String::from(value)))
187 }
188}
189
190#[cfg(feature = "serde")]
191impl serde::Serialize for IString {
192 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
193 <str as serde::Serialize>::serialize(self, serializer)
194 }
195}
196
197#[cfg(feature = "serde")]
198impl<'de> serde::Deserialize<'de> for IString {
199 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
200 <String as serde::Deserialize>::deserialize(deserializer).map(IString::from)
201 }
202}
203
204#[cfg(test)]
205mod test_string {
206 use super::*;
207
208 macro_rules! frame_i_static {
214 ($a:expr) => {
215 IString::Static($a)
216 };
217 }
218
219 macro_rules! frame_i_rc {
220 ($a:expr) => {
221 IString::Rc(Rc::from($a))
222 };
223 }
224
225 macro_rules! frame_deref {
226 ($a:expr) => {
227 *$a
228 };
229 }
230
231 macro_rules! frame_noop {
232 ($a:expr) => {
233 $a
234 };
235 }
236
237 macro_rules! frame_string {
238 ($a:expr) => {
239 String::from($a)
240 };
241 }
242
243 macro_rules! frame_string_ref {
244 ($a:expr) => {
245 &String::from($a)
246 };
247 }
248
249 #[test]
250 fn eq_ne_self() {
251 macro_rules! test_one {
252 ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:expr, $b:expr) => {
253 $macro!($frame1!($a), $frame2!($b));
254 };
255 }
256
257 macro_rules! test_all_frame_combos {
258 ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
259 test_one!($macro!, $frame1!, $frame1!, $a, $b);
261 test_one!($macro!, $frame2!, $frame1!, $a, $b);
262 test_one!($macro!, $frame1!, $frame2!, $a, $b);
263 test_one!($macro!, $frame2!, $frame2!, $a, $b);
264 };
265 ($macro:tt!, $a:literal, $b:literal) => {
266 test_all_frame_combos!($macro!, frame_i_static!, frame_i_rc!, $a, $b);
267 };
268 }
269
270 test_all_frame_combos!(assert_eq!, "foo", "foo");
271 test_all_frame_combos!(assert_ne!, "foo", "bar");
272 }
273
274 #[test]
275 fn cmp_self() {
276 macro_rules! test_one {
277 ($res:expr, $frame1:tt!, $frame2:tt!, $a:expr, $b:expr) => {
278 assert_eq!($res, Ord::cmp(&$frame1!($a), &$frame2!($b)));
279 };
280 }
281
282 macro_rules! test_all_frame_combos {
283 ($res:expr, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
284 test_one!($res, $frame1!, $frame1!, $a, $b);
286 test_one!($res, $frame2!, $frame1!, $a, $b);
287 test_one!($res, $frame1!, $frame2!, $a, $b);
288 test_one!($res, $frame2!, $frame2!, $a, $b);
289 };
290 ($res:expr, $a:literal, $b:literal) => {
291 test_all_frame_combos!($res, frame_i_static!, frame_i_rc!, $a, $b);
292 };
293 }
294
295 test_all_frame_combos!(Ordering::Equal, "foo", "foo");
296 test_all_frame_combos!(Ordering::Greater, "foo", "bar");
297 test_all_frame_combos!(Ordering::Less, "bar", "foo");
298 test_all_frame_combos!(Ordering::Greater, "foobar", "foo");
299 test_all_frame_combos!(Ordering::Less, "foo", "foobar");
300 }
301
302 #[test]
303 fn eq_ne_strings() {
304 macro_rules! test_one {
305 ($macro:tt!, $a:expr, $b:expr) => {
306 $macro!($a, $b);
307 $macro!($b, $a);
308 };
309 }
310
311 macro_rules! test_all_frame_combos {
312 ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
313 test_one!($macro!, $frame1!($a), $frame2!($b));
315 test_one!($macro!, $frame2!($a), $frame1!($b));
316 };
317 ($macro:tt!, $frame2:tt!, $a:literal, $b:literal) => {
318 test_all_frame_combos!($macro!, frame_i_rc!, $frame2!, $a, $b);
319 test_all_frame_combos!($macro!, frame_i_static!, $frame2!, $a, $b);
320 };
321 ($macro:tt!, $a:literal, $b:literal) => {
322 test_all_frame_combos!($macro!, frame_deref!, $a, $b);
323 test_all_frame_combos!($macro!, frame_noop!, $a, $b);
324 test_all_frame_combos!($macro!, frame_string!, $a, $b);
325 test_all_frame_combos!($macro!, frame_string_ref!, $a, $b);
326 };
327 }
328
329 test_all_frame_combos!(assert_eq!, "foo", "foo");
330 test_all_frame_combos!(assert_ne!, "foo", "bar");
331 }
332
333 #[test]
334 fn partial_cmp_strings() {
335 macro_rules! test_one {
336 ($res:expr, $a:expr, $b:expr) => {
337 assert_eq!(Some($res), PartialOrd::partial_cmp(&$a, &$b));
338 };
339 }
340
341 macro_rules! test_all_frame_combos {
342 ($res:expr, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
343 test_one!($res, $frame1!($a), $frame2!($b));
345 test_one!($res, $frame2!($a), $frame1!($b));
346 };
347 ($res:expr, $frame2:tt!, $a:literal, $b:literal) => {
348 test_all_frame_combos!($res, frame_i_rc!, $frame2!, $a, $b);
349 test_all_frame_combos!($res, frame_i_static!, $frame2!, $a, $b);
350 };
351 ($res:expr, $a:literal, $b:literal) => {
352 test_all_frame_combos!($res, frame_deref!, $a, $b);
353 test_all_frame_combos!($res, frame_noop!, $a, $b);
354 test_all_frame_combos!($res, frame_string!, $a, $b);
355 test_all_frame_combos!($res, frame_string_ref!, $a, $b);
356 };
357 }
358
359 test_all_frame_combos!(Ordering::Equal, "foo", "foo");
360 test_all_frame_combos!(Ordering::Greater, "foo", "bar");
361 test_all_frame_combos!(Ordering::Less, "bar", "foo");
362 test_all_frame_combos!(Ordering::Greater, "foobar", "foo");
363 test_all_frame_combos!(Ordering::Less, "foo", "foobar");
364 }
365
366 #[test]
367 fn const_string() {
368 const _STRING: IString = IString::Static("foo");
369 }
370
371 #[test]
372 fn deref_str() {
373 assert_eq!(IString::Static("foo").to_uppercase(), "FOO");
374 assert_eq!(IString::Rc(Rc::from("foo")).to_uppercase(), "FOO");
375 }
376
377 #[test]
378 fn borrow_str() {
379 let map: std::collections::HashMap<_, _> = [
380 (IString::Static("foo"), true),
381 (IString::Rc(Rc::from("bar")), true),
382 ]
383 .into_iter()
384 .collect();
385
386 assert_eq!(map.get("foo").copied(), Some(true));
387 assert_eq!(map.get("bar").copied(), Some(true));
388 }
389
390 #[test]
391 fn as_cow_does_not_clone() {
392 let rc_s = Rc::from("foo");
393
394 let s = IString::Rc(Rc::clone(&rc_s));
395 assert_eq!(Rc::strong_count(&rc_s), 2);
396
397 let cow: Cow<'_, str> = s.as_cow();
398 assert_eq!(Rc::strong_count(&rc_s), 2);
399
400 assert_eq!(cow, "foo");
402 }
403
404 #[test]
405 fn from_ref() {
406 let s = IString::Static("foo");
407 let _out = IString::from(&s);
408 }
409}