Skip to main content

shuck_ast/
name.rs

1//! Compact owned identifier-like strings for the shell AST.
2//!
3//! Adapted from Ruff's `ruff_python_ast::name::Name` implementation:
4//! `/Users/ewhauser/working/ruff/crates/ruff_python_ast/src/name.rs`
5
6use std::borrow::{Borrow, Cow};
7use std::fmt::{Debug, Display, Formatter};
8use std::ops::Deref;
9
10#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
11pub struct Name(compact_str::CompactString);
12
13impl Name {
14    #[inline]
15    pub fn new(name: impl AsRef<str>) -> Self {
16        Self(compact_str::CompactString::new(name))
17    }
18
19    #[inline]
20    pub const fn new_static(name: &'static str) -> Self {
21        Self(compact_str::CompactString::const_new(name))
22    }
23
24    #[inline]
25    pub fn as_str(&self) -> &str {
26        self.0.as_str()
27    }
28}
29
30impl Debug for Name {
31    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32        write!(f, "Name({:?})", self.as_str())
33    }
34}
35
36impl Display for Name {
37    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38        f.write_str(self.as_str())
39    }
40}
41
42impl AsRef<str> for Name {
43    #[inline]
44    fn as_ref(&self) -> &str {
45        self.as_str()
46    }
47}
48
49impl Deref for Name {
50    type Target = str;
51
52    #[inline]
53    fn deref(&self) -> &Self::Target {
54        self.as_str()
55    }
56}
57
58impl Borrow<str> for Name {
59    #[inline]
60    fn borrow(&self) -> &str {
61        self.as_str()
62    }
63}
64
65impl From<&str> for Name {
66    #[inline]
67    fn from(value: &str) -> Self {
68        Self(value.into())
69    }
70}
71
72impl From<String> for Name {
73    #[inline]
74    fn from(value: String) -> Self {
75        Self(value.into())
76    }
77}
78
79impl From<&String> for Name {
80    #[inline]
81    fn from(value: &String) -> Self {
82        Self(value.into())
83    }
84}
85
86impl From<Box<str>> for Name {
87    #[inline]
88    fn from(value: Box<str>) -> Self {
89        Self(value.into())
90    }
91}
92
93impl From<Cow<'_, str>> for Name {
94    #[inline]
95    fn from(value: Cow<'_, str>) -> Self {
96        Self(value.into())
97    }
98}
99
100impl PartialEq<str> for Name {
101    #[inline]
102    fn eq(&self, other: &str) -> bool {
103        self.as_str() == other
104    }
105}
106
107impl PartialEq<Name> for str {
108    #[inline]
109    fn eq(&self, other: &Name) -> bool {
110        other == self
111    }
112}
113
114impl PartialEq<&str> for Name {
115    #[inline]
116    fn eq(&self, other: &&str) -> bool {
117        self.as_str() == *other
118    }
119}
120
121impl PartialEq<Name> for &str {
122    #[inline]
123    fn eq(&self, other: &Name) -> bool {
124        other == self
125    }
126}
127
128impl PartialEq<String> for Name {
129    #[inline]
130    fn eq(&self, other: &String) -> bool {
131        self.as_str() == other
132    }
133}
134
135impl PartialEq<Name> for String {
136    #[inline]
137    fn eq(&self, other: &Name) -> bool {
138        other == self
139    }
140}
141
142impl PartialEq<&String> for Name {
143    #[inline]
144    fn eq(&self, other: &&String) -> bool {
145        self.as_str() == *other
146    }
147}
148
149impl PartialEq<Name> for &String {
150    #[inline]
151    fn eq(&self, other: &Name) -> bool {
152        other == self
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::Name;
159    use std::collections::hash_map::DefaultHasher;
160    use std::hash::{Hash, Hasher};
161
162    #[test]
163    fn new_and_new_static_match() {
164        let dynamic = Name::new("alpha");
165        let static_name = Name::new_static("alpha");
166        assert_eq!(dynamic, static_name);
167        assert_eq!(dynamic.as_str(), "alpha");
168    }
169
170    #[test]
171    fn compares_against_str_and_string() {
172        let name = Name::new("HOME");
173        let string = String::from("HOME");
174
175        assert_eq!(name, "HOME");
176        assert_eq!("HOME", name);
177        assert_eq!(name, string);
178        assert_eq!(string, name);
179    }
180
181    #[test]
182    fn display_and_debug_are_stable() {
183        let name = Name::new("select_var");
184        assert_eq!(format!("{name}"), "select_var");
185        assert_eq!(format!("{name:?}"), "Name(\"select_var\")");
186    }
187
188    #[test]
189    fn ordering_and_hash_follow_string_contents() {
190        let smaller = Name::new("a");
191        let larger = Name::new("b");
192        assert!(smaller < larger);
193
194        let mut left = DefaultHasher::new();
195        smaller.hash(&mut left);
196        let mut right = DefaultHasher::new();
197        Name::new("a").hash(&mut right);
198        assert_eq!(left.finish(), right.finish());
199    }
200
201    #[test]
202    fn supports_short_and_long_inputs() {
203        let short = Name::new("fd");
204        let long = Name::new("this_identifier_is_long_enough_to_spill_past_inline_storage");
205
206        assert_eq!(short.as_str(), "fd");
207        assert_eq!(
208            long.as_str(),
209            "this_identifier_is_long_enough_to_spill_past_inline_storage"
210        );
211    }
212}