pilota_build/
symbol.rs

1use std::{
2    fmt::Display,
3    ops::Deref,
4    sync::{Arc, OnceLock},
5};
6
7use faststr::FastStr;
8use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
9use phf::phf_set;
10
11crate::newtype_index! {
12    pub struct FileId { .. }
13}
14
15crate::newtype_index! {
16    pub struct DefId { .. }
17}
18
19pub static SPECIAL_NAMINGS: OnceLock<Vec<FastStr>> = OnceLock::new();
20
21static KEYWORDS_SET: phf::Set<&'static str> = phf_set![
22    "as", "use", "break", "const", "continue", "crate", "else", "if", "enum", "extern", "false",
23    "fn", "for", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
24    "return", "Self", "self", "static", "struct", "super", "trait", "true", "type", "unsafe",
25    "where", "while", "abstract", "alignof", "become", "box", "do", "final", "macro", "offsetof",
26    "override", "priv", "proc", "pure", "sizeof", "typeof", "unsized", "virtual", "yield", "dyn",
27    "async", "await", "try", "gen"
28];
29
30#[derive(Hash, PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
31pub struct Symbol(pub FastStr);
32
33impl std::borrow::Borrow<str> for Symbol {
34    fn borrow(&self) -> &str {
35        self
36    }
37}
38
39impl Deref for Symbol {
40    type Target = str;
41
42    fn deref(&self) -> &Self::Target {
43        &self.0
44    }
45}
46
47impl<T> From<T> for Symbol
48where
49    T: Into<FastStr>,
50{
51    fn from(t: T) -> Self {
52        Symbol(t.into())
53    }
54}
55
56impl std::fmt::Display for Symbol {
57    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
58        if self.is_path_segment_keyword() {
59            return write!(f, "{}_", &**self);
60        }
61        if KEYWORDS_SET.contains(self) {
62            write!(f, "r#{}", &**self)
63        } else {
64            write!(f, "{}", &**self)
65        }
66    }
67}
68
69impl Symbol {
70    // https://github.com/rust-lang/rust/blob/master/compiler/rustc_span/src/symbol.rs#L2395-L2398
71    fn is_path_segment_keyword(&self) -> bool {
72        ["super", "self", "Self", "crate"].contains(&&**self)
73    }
74}
75
76#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)]
77pub enum EnumRepr {
78    I32,
79}
80
81#[derive(Hash, PartialEq, Eq, Clone, Debug)]
82pub struct Ident {
83    pub sym: Symbol,
84}
85
86impl Ident {
87    pub fn new(sym: Symbol) -> Self {
88        Ident { sym }
89    }
90
91    pub fn raw_str(&self) -> FastStr {
92        self.sym.0.clone()
93    }
94}
95
96impl Deref for Ident {
97    type Target = Symbol;
98
99    fn deref(&self) -> &Self::Target {
100        &self.sym
101    }
102}
103
104impl Display for Ident {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        std::fmt::Display::fmt(&self.sym, f)
107    }
108}
109
110impl<T> From<T> for Ident
111where
112    T: Into<FastStr>,
113{
114    fn from(t: T) -> Self {
115        Ident {
116            sym: Symbol(t.into()),
117        }
118    }
119}
120
121#[derive(Hash, PartialEq, Eq, Clone, Debug, Ord, PartialOrd)]
122pub struct ModPath(Arc<[FastStr]>);
123
124impl Deref for ModPath {
125    type Target = [FastStr];
126
127    fn deref(&self) -> &Self::Target {
128        &self.0
129    }
130}
131
132impl<T> From<T> for ModPath
133where
134    T: Into<Arc<[FastStr]>>,
135{
136    fn from(t: T) -> Self {
137        ModPath(t.into())
138    }
139}
140pub trait IdentName {
141    fn struct_ident(&self) -> FastStr {
142        self.upper_camel_ident()
143    }
144
145    fn enum_ident(&self) -> FastStr {
146        self.upper_camel_ident()
147    }
148
149    fn mod_ident(&self) -> FastStr {
150        self.snake_ident()
151    }
152
153    fn variant_ident(&self) -> FastStr {
154        self.upper_camel_ident()
155    }
156    fn fn_ident(&self) -> FastStr {
157        self.snake_ident()
158    }
159    fn field_ident(&self) -> FastStr {
160        self.snake_ident()
161    }
162    fn const_ident(&self) -> FastStr {
163        self.shouty_snake_case()
164    }
165
166    fn trait_ident(&self) -> FastStr {
167        self.upper_camel_ident()
168    }
169
170    fn newtype_ident(&self) -> FastStr {
171        self.upper_camel_ident()
172    }
173
174    fn upper_camel_ident(&self) -> FastStr;
175    fn snake_ident(&self) -> FastStr;
176    fn shouty_snake_case(&self) -> FastStr;
177}
178
179impl IdentName for &str {
180    fn upper_camel_ident(&self) -> FastStr {
181        if let Some(index) = self.find(|c: char| c != '_') {
182            let s = self[index..].to_upper_camel_case();
183            return format!("{}{}", &self[0..index], s).into();
184        }
185        self.to_string().into()
186    }
187
188    fn snake_ident(&self) -> FastStr {
189        if let Some(index) = self.find(|c: char| c != '_') {
190            let s = &self[index..];
191            let s = if is_common_initialism(s) {
192                to_snake_case(s)
193            } else {
194                s.to_snake_case()
195            };
196            return format!("{}{}", &self[0..index], s).into();
197        }
198        self.to_string().into()
199    }
200
201    fn shouty_snake_case(&self) -> FastStr {
202        if let Some(index) = self.find(|c: char| c != '_') {
203            let s = &self[index..];
204            let s = if is_common_initialism(s) {
205                to_snake_case(s).to_uppercase()
206            } else {
207                s.to_shouty_snake_case()
208            };
209            return format!("{}{}", &self[0..index], s).into();
210        }
211        self.to_string().into()
212    }
213}
214
215impl IdentName for FastStr {
216    fn upper_camel_ident(&self) -> FastStr {
217        (&**self).upper_camel_ident()
218    }
219
220    fn snake_ident(&self) -> FastStr {
221        (&**self).snake_ident()
222    }
223
224    fn shouty_snake_case(&self) -> FastStr {
225        (&**self).shouty_snake_case()
226    }
227}
228
229// Taken from rustc.
230fn to_snake_case(mut str: &str) -> String {
231    let mut words = vec![];
232    // Preserve leading underscores
233    str = str.trim_start_matches(|c: char| {
234        if c == '_' {
235            words.push(String::new());
236            true
237        } else {
238            false
239        }
240    });
241    for s in str.split('_') {
242        let mut last_upper = false;
243        let mut buf = String::new();
244        if s.is_empty() {
245            continue;
246        }
247        for ch in s.chars() {
248            if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
249                words.push(buf);
250                buf = String::new();
251            }
252            last_upper = ch.is_uppercase();
253            buf.extend(ch.to_lowercase());
254        }
255        words.push(buf);
256    }
257    words.join("_")
258}
259
260fn is_common_initialism(s: &str) -> bool {
261    for name in SPECIAL_NAMINGS.get().unwrap_or(&Default::default()).iter() {
262        if s.contains(name.as_str()) {
263            return true;
264        }
265    }
266    false
267}
268
269#[cfg(test)]
270mod tests {
271    use heck::ToSnakeCase;
272
273    use crate::symbol::to_snake_case;
274
275    #[test]
276    fn snake_case() {
277        assert_eq!("IDs".to_snake_case(), "i_ds");
278        // positive
279        assert_eq!(to_snake_case("IDs"), "ids");
280
281        assert_eq!("UIDSecure".to_snake_case(), "uid_secure");
282        // negative
283        assert_eq!(to_snake_case("UIDSecure"), "uidsecure");
284    }
285}