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<&str> for IString {
73 fn from(s: &str) -> IString {
74 IString::Rc(Rc::from(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<std::fmt::Arguments<'_>> for IString {
100 fn from(args: std::fmt::Arguments) -> IString {
101 if let Some(s) = args.as_str() {
102 IString::Static(s)
103 } else {
104 IString::from(args.to_string())
105 }
106 }
107}
108
109impl From<&IString> for IString {
110 fn from(s: &IString) -> IString {
111 s.clone()
112 }
113}
114
115macro_rules! impl_cmp_as_str {
116 (PartialEq::<$type1:ty, $type2:ty>) => {
117 impl_cmp_as_str!(PartialEq::<$type1, $type2>::eq -> bool);
118 };
119 (PartialOrd::<$type1:ty, $type2:ty>) => {
120 impl_cmp_as_str!(PartialOrd::<$type1, $type2>::partial_cmp -> Option<Ordering>);
121 };
122 ($trait:ident :: <$type1:ty, $type2:ty> :: $fn:ident -> $ret:ty) => {
123 impl $trait<$type2> for $type1 {
124 fn $fn(&self, other: &$type2) -> $ret {
125 $trait::$fn(AsRef::<str>::as_ref(self), AsRef::<str>::as_ref(other))
126 }
127 }
128 };
129}
130
131impl Eq for IString {}
132
133impl_cmp_as_str!(PartialEq::<IString, IString>);
134impl_cmp_as_str!(PartialEq::<IString, str>);
135impl_cmp_as_str!(PartialEq::<str, IString>);
136impl_cmp_as_str!(PartialEq::<IString, &str>);
137impl_cmp_as_str!(PartialEq::<&str, IString>);
138impl_cmp_as_str!(PartialEq::<IString, String>);
139impl_cmp_as_str!(PartialEq::<String, IString>);
140impl_cmp_as_str!(PartialEq::<IString, &String>);
141impl_cmp_as_str!(PartialEq::<&String, IString>);
142
143impl Ord for IString {
144 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
145 Ord::cmp(AsRef::<str>::as_ref(self), AsRef::<str>::as_ref(other))
146 }
147}
148
149impl PartialOrd for IString {
152 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
153 Some(self.cmp(other))
154 }
155}
156
157impl_cmp_as_str!(PartialOrd::<IString, str>);
158impl_cmp_as_str!(PartialOrd::<str, IString>);
159impl_cmp_as_str!(PartialOrd::<IString, &str>);
160impl_cmp_as_str!(PartialOrd::<&str, IString>);
161impl_cmp_as_str!(PartialOrd::<IString, String>);
162impl_cmp_as_str!(PartialOrd::<String, IString>);
163impl_cmp_as_str!(PartialOrd::<IString, &String>);
164impl_cmp_as_str!(PartialOrd::<&String, IString>);
165
166impl std::ops::Deref for IString {
167 type Target = str;
168
169 fn deref(&self) -> &Self::Target {
170 self.as_str()
171 }
172}
173
174impl AsRef<str> for IString {
175 fn as_ref(&self) -> &str {
176 self.as_str()
177 }
178}
179
180impl std::hash::Hash for IString {
181 #[inline]
182 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
183 std::hash::Hash::hash(self.as_str(), state)
184 }
185}
186
187impl std::borrow::Borrow<str> for IString {
188 fn borrow(&self) -> &str {
189 self.as_str()
190 }
191}
192
193impl FromStr for IString {
194 type Err = std::convert::Infallible;
195 fn from_str(value: &str) -> Result<Self, Self::Err> {
196 Ok(IString::from(value))
197 }
198}
199
200#[cfg(feature = "serde")]
201impl serde::Serialize for IString {
202 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
203 <str as serde::Serialize>::serialize(self, serializer)
204 }
205}
206
207#[cfg(feature = "serde")]
208impl<'de> serde::Deserialize<'de> for IString {
209 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
210 <String as serde::Deserialize>::deserialize(deserializer).map(IString::from)
211 }
212}
213
214#[cfg(test)]
215mod test_string {
216 use super::*;
217
218 macro_rules! frame_i_static {
224 ($a:expr) => {
225 IString::Static($a)
226 };
227 }
228
229 macro_rules! frame_i_rc {
230 ($a:expr) => {
231 IString::Rc(Rc::from($a))
232 };
233 }
234
235 macro_rules! frame_deref {
236 ($a:expr) => {
237 *$a
238 };
239 }
240
241 macro_rules! frame_noop {
242 ($a:expr) => {
243 $a
244 };
245 }
246
247 macro_rules! frame_string {
248 ($a:expr) => {
249 String::from($a)
250 };
251 }
252
253 macro_rules! frame_string_ref {
254 ($a:expr) => {
255 &String::from($a)
256 };
257 }
258
259 #[test]
260 fn eq_ne_self() {
261 macro_rules! test_one {
262 ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:expr, $b:expr) => {
263 $macro!($frame1!($a), $frame2!($b));
264 };
265 }
266
267 macro_rules! test_all_frame_combos {
268 ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
269 test_one!($macro!, $frame1!, $frame1!, $a, $b);
271 test_one!($macro!, $frame2!, $frame1!, $a, $b);
272 test_one!($macro!, $frame1!, $frame2!, $a, $b);
273 test_one!($macro!, $frame2!, $frame2!, $a, $b);
274 };
275 ($macro:tt!, $a:literal, $b:literal) => {
276 test_all_frame_combos!($macro!, frame_i_static!, frame_i_rc!, $a, $b);
277 };
278 }
279
280 test_all_frame_combos!(assert_eq!, "foo", "foo");
281 test_all_frame_combos!(assert_ne!, "foo", "bar");
282 }
283
284 #[test]
285 fn cmp_self() {
286 macro_rules! test_one {
287 ($res:expr, $frame1:tt!, $frame2:tt!, $a:expr, $b:expr) => {
288 assert_eq!($res, Ord::cmp(&$frame1!($a), &$frame2!($b)));
289 };
290 }
291
292 macro_rules! test_all_frame_combos {
293 ($res:expr, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
294 test_one!($res, $frame1!, $frame1!, $a, $b);
296 test_one!($res, $frame2!, $frame1!, $a, $b);
297 test_one!($res, $frame1!, $frame2!, $a, $b);
298 test_one!($res, $frame2!, $frame2!, $a, $b);
299 };
300 ($res:expr, $a:literal, $b:literal) => {
301 test_all_frame_combos!($res, frame_i_static!, frame_i_rc!, $a, $b);
302 };
303 }
304
305 test_all_frame_combos!(Ordering::Equal, "foo", "foo");
306 test_all_frame_combos!(Ordering::Greater, "foo", "bar");
307 test_all_frame_combos!(Ordering::Less, "bar", "foo");
308 test_all_frame_combos!(Ordering::Greater, "foobar", "foo");
309 test_all_frame_combos!(Ordering::Less, "foo", "foobar");
310 }
311
312 #[test]
313 fn eq_ne_strings() {
314 macro_rules! test_one {
315 ($macro:tt!, $a:expr, $b:expr) => {
316 $macro!($a, $b);
317 $macro!($b, $a);
318 };
319 }
320
321 macro_rules! test_all_frame_combos {
322 ($macro:tt!, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
323 test_one!($macro!, $frame1!($a), $frame2!($b));
325 test_one!($macro!, $frame2!($a), $frame1!($b));
326 };
327 ($macro:tt!, $frame2:tt!, $a:literal, $b:literal) => {
328 test_all_frame_combos!($macro!, frame_i_rc!, $frame2!, $a, $b);
329 test_all_frame_combos!($macro!, frame_i_static!, $frame2!, $a, $b);
330 };
331 ($macro:tt!, $a:literal, $b:literal) => {
332 test_all_frame_combos!($macro!, frame_deref!, $a, $b);
333 test_all_frame_combos!($macro!, frame_noop!, $a, $b);
334 test_all_frame_combos!($macro!, frame_string!, $a, $b);
335 test_all_frame_combos!($macro!, frame_string_ref!, $a, $b);
336 };
337 }
338
339 test_all_frame_combos!(assert_eq!, "foo", "foo");
340 test_all_frame_combos!(assert_ne!, "foo", "bar");
341 }
342
343 #[test]
344 fn partial_cmp_strings() {
345 macro_rules! test_one {
346 ($res:expr, $a:expr, $b:expr) => {
347 assert_eq!(Some($res), PartialOrd::partial_cmp(&$a, &$b));
348 };
349 }
350
351 macro_rules! test_all_frame_combos {
352 ($res:expr, $frame1:tt!, $frame2:tt!, $a:literal, $b:literal) => {
353 test_one!($res, $frame1!($a), $frame2!($b));
355 test_one!($res, $frame2!($a), $frame1!($b));
356 };
357 ($res:expr, $frame2:tt!, $a:literal, $b:literal) => {
358 test_all_frame_combos!($res, frame_i_rc!, $frame2!, $a, $b);
359 test_all_frame_combos!($res, frame_i_static!, $frame2!, $a, $b);
360 };
361 ($res:expr, $a:literal, $b:literal) => {
362 test_all_frame_combos!($res, frame_deref!, $a, $b);
363 test_all_frame_combos!($res, frame_noop!, $a, $b);
364 test_all_frame_combos!($res, frame_string!, $a, $b);
365 test_all_frame_combos!($res, frame_string_ref!, $a, $b);
366 };
367 }
368
369 test_all_frame_combos!(Ordering::Equal, "foo", "foo");
370 test_all_frame_combos!(Ordering::Greater, "foo", "bar");
371 test_all_frame_combos!(Ordering::Less, "bar", "foo");
372 test_all_frame_combos!(Ordering::Greater, "foobar", "foo");
373 test_all_frame_combos!(Ordering::Less, "foo", "foobar");
374 }
375
376 #[test]
377 fn const_string() {
378 const _STRING: IString = IString::Static("foo");
379 }
380
381 #[test]
382 fn deref_str() {
383 assert_eq!(IString::Static("foo").to_uppercase(), "FOO");
384 assert_eq!(IString::Rc(Rc::from("foo")).to_uppercase(), "FOO");
385 }
386
387 #[test]
388 fn borrow_str() {
389 let map: std::collections::HashMap<_, _> = [
390 (IString::Static("foo"), true),
391 (IString::Rc(Rc::from("bar")), true),
392 ]
393 .into_iter()
394 .collect();
395
396 assert_eq!(map.get("foo").copied(), Some(true));
397 assert_eq!(map.get("bar").copied(), Some(true));
398 }
399
400 #[test]
401 fn as_cow_does_not_clone() {
402 let rc_s = Rc::from("foo");
403
404 let s = IString::Rc(Rc::clone(&rc_s));
405 assert_eq!(Rc::strong_count(&rc_s), 2);
406
407 let cow: Cow<'_, str> = s.as_cow();
408 assert_eq!(Rc::strong_count(&rc_s), 2);
409
410 assert_eq!(cow, "foo");
412 }
413
414 #[test]
415 fn from_ref() {
416 let s = IString::Static("foo");
417 let _out = IString::from(&s);
418 }
419
420 #[test]
421 fn from_fmt_arguments() {
422 let s = IString::from(format_args!("Hello World!"));
423 assert!(matches!(s, IString::Static("Hello World!")));
424
425 let name = "Jane";
426 let s = IString::from(format_args!("Hello {name}!"));
427 assert!(matches!(s, IString::Rc(_)));
428 assert_eq!(s, "Hello Jane!");
429 }
430}