|
| 1 | +//! The `SharedString` type and supporting types. |
| 2 | +
|
| 3 | +use std::borrow::Cow; |
| 4 | + |
| 5 | +#[cfg(feature = "track-source")] |
| 6 | +mod source { |
| 7 | + #[derive(Clone)] |
| 8 | + pub struct Source { |
| 9 | + path: std::sync::Arc<std::path::Path>, |
| 10 | + line: usize, |
| 11 | + } |
| 12 | + |
| 13 | + impl std::fmt::Debug for Source { |
| 14 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 15 | + write!(f, "{}:{}", self.path.to_string_lossy(), self.line) |
| 16 | + } |
| 17 | + } |
| 18 | + |
| 19 | + impl Source { |
| 20 | + pub fn new(path: &std::path::Path, line: usize) -> Self { |
| 21 | + Source { |
| 22 | + path: std::sync::Arc::from(path), |
| 23 | + line, |
| 24 | + } |
| 25 | + } |
| 26 | + pub fn get(&self) -> (&std::path::Path, usize) { |
| 27 | + (std::sync::Arc::as_ref(&self.path), self.line) |
| 28 | + } |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +// TODO: Eventually add support for an Arc-like type that uses a thin pointer here. |
| 33 | +// Probably not triomphe::ThinArc, since we'd need to use unsafe to use it. |
| 34 | + |
| 35 | +#[derive(Clone, Debug)] |
| 36 | +enum SharedStringInner { |
| 37 | + Static(&'static str), |
| 38 | + Owned(std::sync::Arc<str>), |
| 39 | +} |
| 40 | + |
| 41 | +impl SharedStringInner { |
| 42 | + #[inline] |
| 43 | + pub fn new(string: &str) -> Self { |
| 44 | + SharedStringInner::Owned(std::sync::Arc::from(string)) |
| 45 | + } |
| 46 | + #[inline] |
| 47 | + pub fn get(&self) -> &str { |
| 48 | + match self { |
| 49 | + SharedStringInner::Static(v) => v, |
| 50 | + SharedStringInner::Owned(v) => std::sync::Arc::as_ref(v), |
| 51 | + } |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +impl Default for SharedStringInner { |
| 56 | + fn default() -> Self { |
| 57 | + SharedStringInner::Static("") |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +impl std::ops::Deref for SharedStringInner { |
| 62 | + type Target = str; |
| 63 | + |
| 64 | + fn deref(&self) -> &Self::Target { |
| 65 | + self.get() |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +/// A shared immutable string type. |
| 70 | +/// |
| 71 | +/// Internally, this is either a `&'static str` or an atomically ref-counted `str`. |
| 72 | +/// It's meant to represent either keys or values with minimal allocations |
| 73 | +/// or duplication of data in memory, in exchange for not supporting mutation |
| 74 | +/// or even in-place slicing. |
| 75 | +/// |
| 76 | +/// With the `track-source` feature, |
| 77 | +/// objects of this type can also track the file and line number they originate from. |
| 78 | +#[derive(Clone, Debug)] |
| 79 | +pub struct SharedString { |
| 80 | + value: SharedStringInner, |
| 81 | + #[cfg(feature = "track-source")] |
| 82 | + source: Option<source::Source>, |
| 83 | +} |
| 84 | + |
| 85 | +/// A `SharedString` equal to `"unset"`. |
| 86 | +pub static UNSET: SharedString = SharedString { |
| 87 | + value: SharedStringInner::Static("unset"), |
| 88 | + #[cfg(feature = "track-source")] |
| 89 | + source: None, |
| 90 | +}; |
| 91 | + |
| 92 | +/// A `SharedString` equal to `""` (an empty string). |
| 93 | +pub static EMPTY: SharedString = SharedString { |
| 94 | + value: SharedStringInner::Static(""), |
| 95 | + #[cfg(feature = "track-source")] |
| 96 | + source: None, |
| 97 | +}; |
| 98 | + |
| 99 | +// Manual-impl (Partial)Eq, (Partial)Ord, and Hash so that the source isn't considered. |
| 100 | + |
| 101 | +impl PartialEq for SharedString { |
| 102 | + fn eq(&self, other: &Self) -> bool { |
| 103 | + *self.value == *other.value |
| 104 | + } |
| 105 | +} |
| 106 | +impl Eq for SharedString {} |
| 107 | +impl PartialOrd for SharedString { |
| 108 | + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| 109 | + Some(self.value.cmp(&other.value)) |
| 110 | + } |
| 111 | +} |
| 112 | +impl Ord for SharedString { |
| 113 | + fn cmp(&self, other: &Self) -> std::cmp::Ordering { |
| 114 | + self.value.cmp(&other.value) |
| 115 | + } |
| 116 | +} |
| 117 | +impl std::hash::Hash for SharedString { |
| 118 | + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 119 | + state.write(self.value.as_bytes()); |
| 120 | + state.write_u8(0); |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +impl std::borrow::Borrow<str> for SharedString { |
| 125 | + fn borrow(&self) -> &str { |
| 126 | + &self.value |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +impl AsRef<str> for SharedString { |
| 131 | + fn as_ref(&self) -> &str { |
| 132 | + &self.value |
| 133 | + } |
| 134 | +} |
| 135 | +impl AsRef<[u8]> for SharedString { |
| 136 | + fn as_ref(&self) -> &[u8] { |
| 137 | + self.value.as_bytes() |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +impl std::ops::Deref for SharedString { |
| 142 | + type Target = str; |
| 143 | + |
| 144 | + fn deref(&self) -> &Self::Target { |
| 145 | + &*self.value |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +impl Default for SharedString { |
| 150 | + fn default() -> Self { |
| 151 | + EMPTY.clone() |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +impl Default for &SharedString { |
| 156 | + fn default() -> Self { |
| 157 | + &EMPTY |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +impl SharedString { |
| 162 | + /// Creates `Self` from the provided string. |
| 163 | + /// |
| 164 | + /// This function copies the string. If the string is `'static`, consider |
| 165 | + /// using [`SharedString::new_static`] instead. |
| 166 | + pub fn new(value: impl AsRef<str>) -> Self { |
| 167 | + SharedString { |
| 168 | + value: SharedStringInner::new(value.as_ref()), |
| 169 | + #[cfg(feature = "track-source")] |
| 170 | + source: None, |
| 171 | + } |
| 172 | + } |
| 173 | + /// As [`SharedString::new`] but only accepts a `&'static str`. |
| 174 | + /// |
| 175 | + /// This function does not copy the string. |
| 176 | + pub fn new_static(value: &'static str) -> Self { |
| 177 | + SharedString { |
| 178 | + value: SharedStringInner::Static(value), |
| 179 | + #[cfg(feature = "track-source")] |
| 180 | + source: None, |
| 181 | + } |
| 182 | + } |
| 183 | + #[cfg(feature = "track-source")] |
| 184 | + /// Returns the path to the file and the line number that this value originates from. |
| 185 | + /// |
| 186 | + /// The line number is 1-indexed to match convention; |
| 187 | + /// the first line will have a line number of 1 rather than 0. |
| 188 | + pub fn source(&self) -> Option<(&std::path::Path, usize)> { |
| 189 | + self.source.as_ref().map(source::Source::get) |
| 190 | + } |
| 191 | + |
| 192 | + #[cfg(feature = "track-source")] |
| 193 | + /// Sets the path and line number from which this value originated. |
| 194 | + /// |
| 195 | + /// The line number should be 1-indexed to match convention; |
| 196 | + /// the first line should have a line number of 1 rather than 0. |
| 197 | + pub fn set_source(&mut self, path: impl AsRef<std::path::Path>, line: usize) { |
| 198 | + self.source = Some(source::Source::new(path.as_ref(), line)) |
| 199 | + } |
| 200 | + |
| 201 | + #[cfg(feature = "track-source")] |
| 202 | + /// Efficiently clones the source from `other`. |
| 203 | + pub fn set_source_from(&mut self, other: &SharedString) { |
| 204 | + self.source = other.source.clone(); |
| 205 | + } |
| 206 | + |
| 207 | + /// Clears the path and line number from which this value originated. |
| 208 | + /// |
| 209 | + /// If the `track-source` feature is not enabled, this function is a no-op. |
| 210 | + pub fn clear_source(&mut self) { |
| 211 | + #[cfg(feature = "track-source")] |
| 212 | + { |
| 213 | + self.source = None; |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + /// Returns a lowercased version of `self`. Will not allocate if `self` is already lowercase. |
| 218 | + #[must_use] |
| 219 | + pub fn into_lowercase(&self) -> Self { |
| 220 | + // TODO: This requires two iterations over the string, and it's definitely possible |
| 221 | + // to do it in one. |
| 222 | + match into_lowercase(self.value.as_ref()) { |
| 223 | + Cow::Borrowed(_) => self.clone(), |
| 224 | + Cow::Owned(v) => { |
| 225 | + #[allow(unused_mut)] |
| 226 | + let mut retval = SharedString::new(v); |
| 227 | + #[cfg(feature = "track-source")] |
| 228 | + retval.set_source_from(self); |
| 229 | + retval |
| 230 | + } |
| 231 | + } |
| 232 | + } |
| 233 | +} |
| 234 | + |
| 235 | +impl std::fmt::Display for SharedString { |
| 236 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 237 | + write!(f, "{}", &*self.value) |
| 238 | + } |
| 239 | +} |
| 240 | + |
| 241 | +impl From<&str> for SharedString { |
| 242 | + fn from(value: &str) -> Self { |
| 243 | + Self::new(value) |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +pub(crate) fn into_lowercase(string: &str) -> std::borrow::Cow<str> { |
| 248 | + // TODO: This requires two iterations over the string, and it's definitely possible |
| 249 | + // to do it in one. |
| 250 | + if string.chars().all(char::is_lowercase) { |
| 251 | + std::borrow::Cow::Borrowed(string) |
| 252 | + } else { |
| 253 | + std::borrow::Cow::Owned(string.to_lowercase()) |
| 254 | + } |
| 255 | +} |
0 commit comments