Skip to main content

ion_rs/types/
symbol.rs

1use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
2use crate::result::IonFailure;
3use crate::{IonResult, SymbolRef};
4use std::borrow::Borrow;
5use std::cmp::Ordering;
6use std::fmt::{Display, Formatter};
7use std::hash::{Hash, Hasher};
8use std::sync::Arc;
9
10/// Stores or points to the text of a given [Symbol].
11#[derive(Debug, Eq)]
12pub(crate) enum SymbolText {
13    // This Symbol refers to a string in the active symbol table
14    Shared(Arc<str>),
15    // This Symbol owns its own text
16    // TODO: Turn this into a Box<str>.
17    //       Symbols are read-only, so there's no chance we'll add data to the `String`. Using
18    //       a `Box<str>` shrinks this value from 24 bytes to 8 bytes.
19    Owned(String),
20    // This symbol has text that is statically defined (e.g. system symbol table text)
21    Static(&'static str),
22    // This Symbol is equivalent to SID zero (`$0`)
23    Unknown,
24}
25
26impl SymbolText {
27    fn text(&self) -> Option<&str> {
28        let text = match self {
29            SymbolText::Shared(s) => s.as_ref(),
30            SymbolText::Owned(s) => s.as_str(),
31            SymbolText::Static(s) => s,
32            SymbolText::Unknown => return None,
33        };
34        Some(text)
35    }
36}
37
38impl Hash for SymbolText {
39    fn hash<H: Hasher>(&self, state: &mut H) {
40        let text = self.text().unwrap_or("");
41        text.hash(state)
42    }
43}
44
45impl Clone for SymbolText {
46    fn clone(&self) -> Self {
47        match self {
48            SymbolText::Owned(text) => SymbolText::Owned(text.to_owned()),
49            SymbolText::Shared(text) => SymbolText::Shared(Arc::clone(text)),
50            SymbolText::Static(text) => SymbolText::Static(text),
51            SymbolText::Unknown => SymbolText::Unknown,
52        }
53    }
54}
55
56impl PartialEq<Self> for SymbolText {
57    fn eq(&self, other: &Self) -> bool {
58        self.cmp(other) == Ordering::Equal
59    }
60}
61
62impl PartialOrd<Self> for SymbolText {
63    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64        Some(self.cmp(other))
65    }
66}
67
68impl Ord for SymbolText {
69    fn cmp(&self, other: &Self) -> Ordering {
70        match (self.text(), other.text()) {
71            // If both Symbols have known text, delegate the comparison to their text.
72            (Some(s1), Some(s2)) => s1.cmp(s2),
73            // Otherwise, $0 (unknown text) is treated as 'less than' known text
74            (Some(_), None) => Ordering::Greater,
75            (None, Some(_)) => Ordering::Less,
76            (None, None) => Ordering::Equal,
77        }
78    }
79}
80
81/// The text of a fully resolved field name, annotation, or symbol value. If the symbol has known
82/// text (that is: the symbol is not `$0`), it will be stored as either a `String` or a shared
83/// reference to text in a symbol table.
84#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
85pub struct Symbol {
86    pub(crate) text: SymbolText,
87}
88
89impl Symbol {
90    pub fn owned<I: Into<String>>(text: I) -> Symbol {
91        Symbol {
92            text: SymbolText::Owned(text.into()),
93        }
94    }
95
96    pub fn shared(text: Arc<str>) -> Symbol {
97        Symbol {
98            text: SymbolText::Shared(text),
99        }
100    }
101
102    pub const fn static_text(text: &'static str) -> Symbol {
103        Symbol {
104            text: SymbolText::Static(text),
105        }
106    }
107
108    pub fn unknown_text() -> Symbol {
109        Symbol {
110            text: SymbolText::Unknown,
111        }
112    }
113
114    pub fn text(&self) -> Option<&str> {
115        self.text.text()
116    }
117
118    pub fn expect_text(&self) -> IonResult<&str> {
119        match self.text() {
120            Some(text) => Ok(text),
121            None => IonResult::decoding_error("symbol has unknown text"),
122        }
123    }
124}
125
126impl IonEq for Symbol {
127    fn ion_eq(&self, other: &Self) -> bool {
128        self == other
129    }
130}
131
132impl IonDataOrd for Symbol {
133    fn ion_cmp(&self, other: &Self) -> Ordering {
134        self.cmp(other)
135    }
136}
137
138impl IonDataHash for Symbol {
139    fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
140        self.hash(state)
141    }
142}
143
144// We cannot use a blanket impl for AsRef<str> as that would prevent us from
145// optimizing the From<String> case, a conversion which can be performed
146// without cloning.
147
148impl From<&str> for Symbol {
149    fn from(text: &str) -> Self {
150        Symbol::owned(text)
151    }
152}
153
154impl From<String> for Symbol {
155    fn from(text: String) -> Self {
156        Symbol::owned(text)
157    }
158}
159
160impl From<&String> for Symbol {
161    fn from(text: &String) -> Self {
162        text.as_str().into()
163    }
164}
165
166impl<'a> From<&'a Symbol> for Symbol {
167    fn from(text: &'a Symbol) -> Self {
168        text.clone()
169    }
170}
171
172impl<'a> From<SymbolRef<'a>> for Symbol {
173    fn from(symbol_ref: SymbolRef<'a>) -> Self {
174        symbol_ref.to_owned()
175    }
176}
177
178impl Display for Symbol {
179    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
180        match self.text() {
181            None => write!(f, "$0"),
182            Some(text) => write!(f, "'{text}'"),
183        }
184    }
185}
186
187impl<A: AsRef<str>> PartialEq<A> for Symbol {
188    fn eq(&self, other: &A) -> bool {
189        self.text()
190            // If the symbol has known text, compare it to the provide text
191            .map(|t| t == other.as_ref())
192            // If there's no text, it's definitely not equivalent to the provided text
193            .unwrap_or(false)
194    }
195}
196
197impl PartialEq<Symbol> for String {
198    fn eq(&self, other: &Symbol) -> bool {
199        other.text().map(|t| t == self.as_str()).unwrap_or(false)
200    }
201}
202
203impl PartialEq<Symbol> for &str {
204    fn eq(&self, other: &Symbol) -> bool {
205        other.text().map(|t| &t == self).unwrap_or(false)
206    }
207}
208
209// Note that if the Symbol has no text, this will return the empty string. This is unfortunate,
210// but implementing this trait is necessary to allow `Symbol` to be used as a HashMap key.
211// See issue https://github.com/amazon-ion/ion-rust/issues/444 for more information.
212impl Borrow<str> for Symbol {
213    fn borrow(&self) -> &str {
214        // If the
215        self.text().unwrap_or("")
216    }
217}
218
219#[cfg(test)]
220mod symbol_tests {
221    use super::*;
222
223    /// This is a test to ensure that the static_text function is const.
224    const TEST_STATIC_TEXT_IS_CONST: Symbol = Symbol::static_text("foo");
225
226    #[test]
227    fn test_static_text_is_const() {
228        // Uses the above `const` to prevent it from being considered dead code.
229        assert_eq!(TEST_STATIC_TEXT_IS_CONST, "foo")
230    }
231
232    #[test]
233    fn ordering_and_eq() {
234        let mut symbols = vec![
235            Symbol::owned("foo"),
236            Symbol::shared(Arc::from("bar")),
237            Symbol::shared(Arc::from("baz")),
238            Symbol::owned("quux"),
239        ];
240        // Sort the list to demonstrate that our Ord implementation works.
241        symbols.as_mut_slice().sort();
242        // Equality testing doesn't depend on what kind of Symbol it is, just the text.
243        // We can compare the sorted version of the vec above to this one and it will
244        // be considered equal.
245        let expected = vec![
246            Symbol::owned("bar"),
247            Symbol::owned("baz"),
248            Symbol::owned("foo"),
249            Symbol::owned("quux"),
250        ];
251        assert_eq!(symbols, expected)
252    }
253
254    #[test]
255    fn partial_eq_str() {
256        let symbols = vec![
257            Symbol::shared(Arc::from("bar")),
258            Symbol::shared(Arc::from("baz")),
259            Symbol::owned("foo"),
260            Symbol::owned("quux"),
261        ];
262        // Equality testing for a rust str with symbol
263        let expected = vec!["bar", "baz", "foo", "quux"];
264        assert_eq!(symbols, expected)
265    }
266}