Skip to main content

ocelot_base/
shared_string.rs

1//! Shared string wrapper type for efficient string handling.
2
3use crate::result::OcelotResult;
4use ecow::EcoString;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7/// A wrapper around ecow::EcoString for efficient shared string storage.
8///
9/// This type provides copy-on-write semantics with cheap cloning,
10/// making it ideal for storing strings that are shared across multiple
11/// parts of the compiler without unnecessary allocations.
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct SharedString(pub EcoString);
14
15impl SharedString {
16    /// Creates a new SharedString from the given string.
17    pub fn new(s: impl Into<String>) -> Self {
18        Self(EcoString::from(s.into()))
19    }
20
21    /// Creates a new empty SharedString.
22    pub fn empty() -> Self {
23        Self(EcoString::from(""))
24    }
25
26    /// Clears the contents of the string.
27    pub fn clear(&mut self) {
28        self.0 = EcoString::from("");
29    }
30
31    /// Appends a string slice to this string.
32    pub fn push_str(&mut self, string: &str) {
33        let mut new_string = self.0.to_string();
34        new_string.push_str(string);
35        self.0 = EcoString::from(new_string);
36    }
37
38    /// Returns the underlying string as a string slice.
39    pub fn as_str(&self) -> &str {
40        &self.0
41    }
42
43    /// Returns the length of the string.
44    pub fn len(&self) -> usize {
45        self.0.len()
46    }
47
48    /// Returns true if the string is empty.
49    pub fn is_empty(&self) -> bool {
50        self.0.is_empty()
51    }
52
53    pub fn from_utf8(ut8_bytes: &[u8]) -> OcelotResult<Self> {
54        Ok(Self(EcoString::from(str::from_utf8(ut8_bytes)?)))
55    }
56}
57
58/// Creates a new SharedString from a format string.
59///
60/// This macro delegates to `eco_format` from the ecow crate.
61#[macro_export]
62macro_rules! shared_format {
63    ($($arg:tt)*) => {
64        $crate::shared_string::SharedString(ecow::eco_format!($($arg)*))
65    };
66}
67
68impl Default for SharedString {
69    fn default() -> Self {
70        Self::empty()
71    }
72}
73
74impl From<String> for SharedString {
75    fn from(s: String) -> Self {
76        Self(EcoString::from(s))
77    }
78}
79
80impl From<&str> for SharedString {
81    fn from(s: &str) -> Self {
82        Self(EcoString::from(s))
83    }
84}
85
86impl From<Box<str>> for SharedString {
87    fn from(s: Box<str>) -> Self {
88        Self(EcoString::from(&*s))
89    }
90}
91
92impl From<&SharedString> for SharedString {
93    fn from(s: &SharedString) -> Self {
94        s.clone()
95    }
96}
97
98impl AsRef<str> for SharedString {
99    fn as_ref(&self) -> &str {
100        &self.0
101    }
102}
103
104impl std::borrow::Borrow<str> for SharedString {
105    fn borrow(&self) -> &str {
106        &self.0
107    }
108}
109
110impl std::fmt::Display for SharedString {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        write!(f, "{}", self.0)
113    }
114}
115
116impl std::fmt::Debug for SharedString {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "`{}`", self.0)
119    }
120}
121
122impl std::ops::Deref for SharedString {
123    type Target = str;
124
125    fn deref(&self) -> &Self::Target {
126        &self.0
127    }
128}
129
130impl PartialEq<str> for SharedString {
131    fn eq(&self, other: &str) -> bool {
132        self.as_str() == other
133    }
134}
135
136impl PartialEq<&str> for SharedString {
137    fn eq(&self, other: &&str) -> bool {
138        self.as_str() == *other
139    }
140}
141
142impl PartialEq<SharedString> for str {
143    fn eq(&self, other: &SharedString) -> bool {
144        self == other.as_str()
145    }
146}
147
148impl PartialEq<SharedString> for &str {
149    fn eq(&self, other: &SharedString) -> bool {
150        *self == other.as_str()
151    }
152}
153
154impl Serialize for SharedString {
155    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156    where
157        S: Serializer,
158    {
159        serializer.serialize_str(self.as_str())
160    }
161}
162
163impl<'de> Deserialize<'de> for SharedString {
164    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165    where
166        D: Deserializer<'de>,
167    {
168        let value = String::deserialize(deserializer)?;
169        Ok(value.into())
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_shared_string_creation() {
179        let s1 = SharedString::new("hello");
180        let s2 = SharedString::from("world");
181        let s3: SharedString = "test".into();
182
183        assert_eq!(s1.as_str(), "hello");
184        assert_eq!(s2.as_str(), "world");
185        assert_eq!(s3.as_str(), "test");
186    }
187
188    #[test]
189    fn test_shared_string_equality() {
190        let s1 = SharedString::new("hello");
191        let s2 = SharedString::new("hello");
192        let s3 = SharedString::new("world");
193
194        assert_eq!(s1, s2);
195        assert_ne!(s1, s3);
196    }
197
198    #[test]
199    fn test_shared_string_clone() {
200        let s1 = SharedString::new("hello");
201        let s2 = s1.clone();
202
203        assert_eq!(s1, s2);
204        assert_eq!(s1.as_str(), s2.as_str());
205    }
206
207    #[test]
208    fn test_shared_string_default() {
209        let s = SharedString::default();
210        assert!(s.is_empty());
211        assert_eq!(s.len(), 0);
212    }
213
214    #[test]
215    fn test_shared_string_deref() {
216        let s = SharedString::new("hello");
217        assert_eq!(s.len(), 5);
218        assert_eq!(&s[0..2], "he");
219    }
220
221    #[test]
222    fn test_shared_string_display() {
223        let s = SharedString::new("hello");
224        assert_eq!(format!("{}", s), "hello");
225    }
226
227    #[test]
228    fn test_shared_string_format_macro() {
229        let name = "world";
230        let s = shared_format!("Hello, {}!", name);
231        assert_eq!(s.as_str(), "Hello, world!");
232    }
233
234    #[test]
235    fn test_shared_string_format_macro_multiple_args() {
236        let s = shared_format!("{} + {} = {}", 1, 2, 3);
237        assert_eq!(s.as_str(), "1 + 2 = 3");
238    }
239}