Skip to main content

swc_atoms/
lib.rs

1//! See [Atom] and [UnsafeAtom]
2
3#![allow(clippy::unreadable_literal)]
4
5#[doc(hidden)]
6/// Not a public API.
7pub extern crate hstr;
8#[doc(hidden)]
9/// Not a public API.
10pub extern crate once_cell;
11
12use std::{
13    borrow::{Borrow, Cow},
14    cell::UnsafeCell,
15    fmt::{self, Display, Formatter},
16    hash::{Hash, Hasher},
17    mem::transmute,
18    ops::Deref,
19    rc::Rc,
20};
21
22pub use hstr::wtf8;
23use once_cell::sync::Lazy;
24use serde::Serializer;
25use wtf8::Wtf8;
26
27pub use crate::{fast::UnsafeAtom, wtf8_atom::Wtf8Atom};
28
29mod fast;
30mod wtf8_atom;
31
32/// Clone-on-write string.
33///
34///
35/// See [tendril] for more details.
36#[derive(Clone, Default)]
37#[repr(transparent)]
38pub struct Atom(hstr::Atom);
39
40impl PartialEq for Atom {
41    #[inline]
42    fn eq(&self, other: &Self) -> bool {
43        self.0 == other.0
44    }
45}
46
47impl Eq for Atom {}
48
49impl Hash for Atom {
50    #[inline]
51    fn hash<H: Hasher>(&self, state: &mut H) {
52        self.0.hash(state);
53    }
54}
55
56#[cfg(feature = "encoding-impl")]
57impl cbor4ii::core::enc::Encode for Atom {
58    #[inline]
59    fn encode<W: cbor4ii::core::enc::Write>(
60        &self,
61        writer: &mut W,
62    ) -> Result<(), cbor4ii::core::enc::Error<W::Error>> {
63        self.as_str().encode(writer)
64    }
65}
66
67#[cfg(feature = "encoding-impl")]
68impl<'de> cbor4ii::core::dec::Decode<'de> for Atom {
69    #[inline]
70    fn decode<R: cbor4ii::core::dec::Read<'de>>(
71        reader: &mut R,
72    ) -> Result<Self, cbor4ii::core::dec::Error<R::Error>> {
73        let s = <&str>::decode(reader)?;
74        Ok(Atom::new(s))
75    }
76}
77
78#[cfg(feature = "arbitrary")]
79#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
80impl<'a> arbitrary::Arbitrary<'a> for Atom {
81    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
82        let sym = u.arbitrary::<String>()?;
83        if sym.is_empty() {
84            return Err(arbitrary::Error::NotEnoughData);
85        }
86        Ok(Self(hstr::Atom::from(sym)))
87    }
88}
89
90fn _asserts() {
91    // let _static_assert_size_eq = std::mem::transmute::<Atom, [usize; 1]>;
92
93    fn _assert_send<T: Send>() {}
94    fn _assert_sync<T: Sync>() {}
95
96    _assert_send::<Atom>();
97    _assert_sync::<Atom>();
98}
99
100impl Atom {
101    /// Creates a new [Atom] from a string.
102    #[inline(always)]
103    pub fn new<S>(s: S) -> Self
104    where
105        hstr::Atom: From<S>,
106    {
107        Atom(hstr::Atom::from(s))
108    }
109
110    #[inline]
111    pub fn to_ascii_lowercase(&self) -> Self {
112        Self(self.0.to_ascii_lowercase())
113    }
114
115    #[inline]
116    pub fn as_str(&self) -> &str {
117        &self.0
118    }
119
120    /// Converts a WTF-8 encoded [Wtf8Atom] to a regular UTF-8 [Atom] without
121    /// validation.
122    ///
123    /// # Safety
124    ///
125    /// The caller must ensure that the WTF-8 atom contains only valid UTF-8
126    /// data (no unpaired surrogates). This function performs no validation
127    /// and will create an invalid `Atom` if the input contains unpaired
128    /// surrogates.
129    ///
130    /// This is a zero-cost conversion that preserves all internal optimizations
131    /// (inline storage, precomputed hashes, etc.) since both types have
132    /// identical internal representation.
133    pub unsafe fn from_wtf8_unchecked(s: Wtf8Atom) -> Self {
134        Atom(unsafe { hstr::Atom::from_wtf8_unchecked(s.0) })
135    }
136}
137
138impl Deref for Atom {
139    type Target = str;
140
141    #[inline]
142    fn deref(&self) -> &Self::Target {
143        &self.0
144    }
145}
146
147macro_rules! impl_eq {
148    ($T:ty) => {
149        impl PartialEq<$T> for Atom {
150            #[inline]
151            fn eq(&self, other: &$T) -> bool {
152                &**self == &**other
153            }
154        }
155    };
156}
157
158macro_rules! impl_from {
159    ($T:ty) => {
160        impl From<$T> for Atom {
161            fn from(s: $T) -> Self {
162                Atom::new(s)
163            }
164        }
165    };
166}
167
168impl From<hstr::Atom> for Atom {
169    #[inline(always)]
170    fn from(s: hstr::Atom) -> Self {
171        Atom(s)
172    }
173}
174
175impl From<Atom> for hstr::Wtf8Atom {
176    #[inline(always)]
177    fn from(s: Atom) -> Self {
178        hstr::Wtf8Atom::from(&*s)
179    }
180}
181
182impl PartialEq<str> for Atom {
183    #[inline]
184    fn eq(&self, other: &str) -> bool {
185        self.0 == *other
186    }
187}
188
189impl_eq!(&'_ str);
190impl_eq!(Box<str>);
191impl_eq!(std::sync::Arc<str>);
192impl_eq!(Rc<str>);
193impl_eq!(Cow<'_, str>);
194impl_eq!(String);
195
196impl_from!(&'_ str);
197impl_from!(Box<str>);
198impl_from!(String);
199impl_from!(Cow<'_, str>);
200
201impl AsRef<str> for Atom {
202    fn as_ref(&self) -> &str {
203        self
204    }
205}
206
207impl fmt::Debug for Atom {
208    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
209        fmt::Debug::fmt(&**self, f)
210    }
211}
212
213impl Display for Atom {
214    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
215        Display::fmt(&**self, f)
216    }
217}
218
219impl PartialOrd for Atom {
220    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
221        Some(self.cmp(other))
222    }
223}
224
225impl Ord for Atom {
226    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
227        self.as_str().cmp(other.as_str())
228    }
229}
230
231impl Borrow<Wtf8Atom> for Atom {
232    fn borrow(&self) -> &Wtf8Atom {
233        // SAFETY:
234        // 1. Wtf8Atom is #[repr(transparent)] over hstr::Wtf8Atom, so as hstr::Wtf8Atom
235        //    over TaggedValue
236        // 2. Atom is #[repr(transparent)] over hstr::Atom, so as hstr::Atom over
237        //    TaggedValue
238        // 3. hstr::Atom and hstr::Wtf8Atom share the same TaggedValue
239        const _: () = assert!(std::mem::size_of::<Atom>() == std::mem::size_of::<Wtf8Atom>());
240        const _: () = assert!(std::mem::align_of::<Atom>() == std::mem::align_of::<Wtf8Atom>());
241        unsafe { transmute::<&Atom, &Wtf8Atom>(self) }
242    }
243}
244
245impl serde::ser::Serialize for Atom {
246    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
247    where
248        S: Serializer,
249    {
250        serializer.serialize_str(self)
251    }
252}
253
254impl<'de> serde::de::Deserialize<'de> for Atom {
255    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256    where
257        D: serde::Deserializer<'de>,
258    {
259        String::deserialize(deserializer).map(Self::new)
260    }
261}
262
263/// Creates an [Atom] from a constant.
264#[macro_export]
265macro_rules! atom {
266    ($s:tt) => {{
267        $crate::Atom::from($crate::hstr::atom!($s))
268    }};
269}
270
271/// Creates an [Atom] from a constant.
272#[macro_export]
273macro_rules! lazy_atom {
274    ($s:tt) => {{
275        $crate::Atom::from($crate::hstr::atom!($s))
276    }};
277}
278
279impl PartialEq<Atom> for str {
280    #[inline]
281    fn eq(&self, other: &Atom) -> bool {
282        *self == other.0
283    }
284}
285
286#[doc(hidden)]
287pub type CahcedAtom = Lazy<Atom>;
288
289/// This should be used as a key for hash maps and hash sets.
290///
291/// This will be replaced with [Atom] in the future.
292pub type StaticString = Atom;
293
294#[derive(Default)]
295pub struct AtomStore(hstr::AtomStore);
296
297impl AtomStore {
298    #[inline]
299    pub fn atom<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Atom {
300        Atom(self.0.atom(s))
301    }
302
303    #[inline]
304    pub fn wtf8_atom<'a>(&mut self, s: impl Into<Cow<'a, Wtf8>>) -> Wtf8Atom {
305        Wtf8Atom(self.0.wtf8_atom(s))
306    }
307}
308
309/// A fast internally mutable cell for [AtomStore].
310#[derive(Default)]
311pub struct AtomStoreCell(UnsafeCell<AtomStore>);
312
313impl AtomStoreCell {
314    #[inline]
315    pub fn atom<'a>(&self, s: impl Into<Cow<'a, str>>) -> Atom {
316        // evaluate the into before borrowing (see #8362)
317        let s: Cow<'a, str> = s.into();
318        // SAFETY: We can skip the borrow check of RefCell because
319        // this API enforces a safe contract. It is slightly faster
320        // to use an UnsafeCell. Note the borrow here is short lived
321        // only to this block.
322        unsafe { (*self.0.get()).atom(s) }
323    }
324
325    #[inline]
326    pub fn wtf8_atom<'a>(&self, s: impl Into<Cow<'a, Wtf8>>) -> Wtf8Atom {
327        // evaluate the into before borrowing (see #8362)
328        let s: Cow<'a, Wtf8> = s.into();
329        // SAFETY: We can skip the borrow check of RefCell because
330        // this API enforces a safe contract. It is slightly faster
331        // to use an UnsafeCell. Note the borrow here is short lived
332        // only to this block.
333        unsafe { (*self.0.get()).wtf8_atom(s) }
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn equality_variants() {
343        assert_eq!(Atom::from("inline"), Atom::from("inline"));
344        assert_ne!(Atom::from("inline"), Atom::from("inlinf"));
345
346        let mut store = AtomStore::default();
347        let dynamic = store.atom("Hello, beautiful world!");
348        let dynamic_same_store_equal = store.atom("Hello, beautiful world!");
349
350        let mut other_store = AtomStore::default();
351        let dynamic_other_store_equal = other_store.atom("Hello, beautiful world!");
352        let dynamic_different = other_store.atom("Hello, different world!");
353
354        assert_eq!(dynamic, dynamic_same_store_equal);
355        assert_eq!(dynamic, dynamic_other_store_equal);
356        assert_ne!(dynamic, dynamic_different);
357        assert_eq!(dynamic, "Hello, beautiful world!");
358    }
359}
360
361/// noop
362#[cfg(feature = "shrink-to-fit")]
363impl shrink_to_fit::ShrinkToFit for Atom {
364    #[inline(always)]
365    fn shrink_to_fit(&mut self) {}
366}