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