xsd_parser/models/code/
ident_path.rs

1use std::ops::{Deref, DerefMut};
2use std::str::FromStr;
3
4use proc_macro2::{Ident as Ident2, TokenStream};
5use quote::{format_ident, quote, ToTokens};
6use smallvec::SmallVec;
7use thiserror::Error;
8
9use crate::models::{meta::MetaTypes, schema::NamespaceId};
10
11use super::format_module_ident;
12
13/// Represents an identifier path.
14///
15/// A identifier path is the full path of a specific identifier in the code,
16/// like `std::str::FromStr`. The identified object can be a constant, a type,
17/// a trait or anything else that is defined within a module.
18///
19/// The identifier path contains two parts:
20/// - The identifier itself, which is more or less the name of the object to identify, and
21/// - the math of the module the object is provided at.
22#[derive(Debug, Clone, Eq, PartialEq)]
23pub struct IdentPath {
24    path: Option<ModulePath>,
25    ident: Ident2,
26}
27
28/// Represents a path of a module.
29///
30/// The module path is a chain ob module names separated by a double colon like
31/// `std::str`. It is used to identify modules inside the code.
32#[derive(Default, Debug, Clone, Eq, PartialEq)]
33pub struct ModulePath(pub SmallVec<[Ident2; 2]>);
34
35/// Error that is raised by [`IdentPath`] if parsing the value from string failed.
36#[derive(Debug, Error)]
37#[error("Invalid identifier path: {0}")]
38pub struct InvalidIdentPath(pub String);
39
40impl IdentPath {
41    /// Crates a new [`IdentPath`] instance from the passed module `path` and the
42    /// `ident`ifier of the object to refer to.
43    #[must_use]
44    pub fn from_parts<I>(path: I, ident: Ident2) -> Self
45    where
46        I: IntoIterator<Item = Ident2>,
47    {
48        Self::from_ident(ident).with_path(path)
49    }
50
51    /// Creates a new [`IdentPath`] from the passed object `ident`ifier without a
52    /// module path.
53    #[must_use]
54    pub fn from_ident(ident: Ident2) -> Self {
55        Self { ident, path: None }
56    }
57
58    /// Changes the identifier of this identifier path to the passed `ident`.
59    #[must_use]
60    pub fn with_ident(mut self, ident: Ident2) -> Self {
61        self.ident = ident;
62
63        self
64    }
65
66    /// Changes the module path of this identifier path to the passed `path`.
67    #[must_use]
68    pub fn with_path<I>(mut self, path: I) -> Self
69    where
70        I: IntoIterator<Item = Ident2>,
71    {
72        self.path = Some(ModulePath(path.into_iter().collect()));
73
74        self
75    }
76
77    /// Splits this identifier path into it's two main parts, the identifier
78    /// and the module path.
79    #[must_use]
80    pub fn into_parts(self) -> (Ident2, Option<ModulePath>) {
81        let Self { ident, path } = self;
82
83        (ident, path)
84    }
85
86    /// Returns the identifier of this identifier path.
87    #[must_use]
88    pub fn ident(&self) -> &Ident2 {
89        &self.ident
90    }
91
92    /// Creates a new [`IdentPath`] that is relative to the passed `dst` module path.
93    ///
94    /// This uses the `super` keyword to create a relative path from the passed `dst` module path
95    /// and this identifier path. The relative path is returned as token stream.
96    #[must_use]
97    pub fn relative_to(&self, dst: &ModulePath) -> TokenStream {
98        let ident = &self.ident;
99
100        let Some(src) = &self.path else {
101            return quote!(#ident);
102        };
103
104        let mut ret = TokenStream::new();
105        let mut src = src.0.iter().fuse();
106        let mut dst = dst.0.iter().fuse();
107
108        macro_rules! push {
109            ($x:expr) => {{
110                let x = $x;
111                if ret.is_empty() {
112                    ret.extend(x)
113                } else {
114                    ret.extend(quote!(::#x))
115                }
116            }};
117        }
118
119        loop {
120            match (src.next(), dst.next()) {
121                (Some(a), Some(b)) if a == b => {}
122                (Some(a), Some(_)) => {
123                    push!(quote!(super));
124                    while dst.next().is_some() {
125                        push!(quote!(super));
126                    }
127
128                    push!(quote!(#a));
129                    for a in src {
130                        push!(quote!(#a));
131                    }
132
133                    push!(quote!(#ident));
134
135                    return ret;
136                }
137                (Some(a), None) => push!(quote!(#a)),
138                (None, Some(_)) => push!(quote!(super)),
139                (None, None) => {
140                    push!(quote!(#ident));
141                    return ret;
142                }
143            }
144        }
145    }
146}
147
148impl TryFrom<&str> for IdentPath {
149    type Error = InvalidIdentPath;
150
151    fn try_from(value: &str) -> Result<Self, Self::Error> {
152        Self::from_str(value)
153    }
154}
155
156impl TryFrom<String> for IdentPath {
157    type Error = InvalidIdentPath;
158
159    fn try_from(value: String) -> Result<Self, Self::Error> {
160        Self::from_str(&value)
161    }
162}
163
164impl FromStr for IdentPath {
165    type Err = InvalidIdentPath;
166
167    fn from_str(s: &str) -> Result<Self, Self::Err> {
168        let mut ident = None;
169        let mut path = ModulePath::default();
170
171        for part in s.split("::") {
172            let part = part.trim();
173            if part.is_empty() {
174                continue;
175            }
176
177            if let Some(ident) = ident.take() {
178                path.0.push(ident);
179            }
180
181            ident = Some(format_ident!("{part}"));
182        }
183
184        Ok(Self {
185            ident: ident.ok_or_else(|| InvalidIdentPath(s.into()))?,
186            path: Some(path),
187        })
188    }
189}
190
191impl ToTokens for IdentPath {
192    fn to_tokens(&self, tokens: &mut TokenStream) {
193        if let Some(path) = &self.path {
194            for module in &path.0 {
195                tokens.extend(quote!(#module::));
196            }
197        }
198
199        let ident = self.ident();
200
201        tokens.extend(quote!(#ident));
202    }
203}
204
205impl ModulePath {
206    /// Create a new [`ModulePath`] instance from the passed namespace id `ns` and the
207    /// `types` information.
208    ///
209    /// This tries to look up the passed namespace id in the types information and create
210    /// a module path for it.
211    #[must_use]
212    pub fn from_namespace(ns: Option<NamespaceId>, types: &MetaTypes) -> Self {
213        let ident = ns
214            .and_then(|id| types.modules.get(&id))
215            .and_then(|module| module.name.as_ref())
216            .map(format_module_ident);
217
218        Self(ident.into_iter().collect())
219    }
220
221    /// Add a module to the module
222    #[must_use]
223    pub fn join(mut self, other: Ident2) -> Self {
224        self.0.push(other);
225
226        self
227    }
228}
229
230impl Deref for ModulePath {
231    type Target = SmallVec<[Ident2; 2]>;
232
233    fn deref(&self) -> &Self::Target {
234        &self.0
235    }
236}
237
238impl DerefMut for ModulePath {
239    fn deref_mut(&mut self) -> &mut Self::Target {
240        &mut self.0
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use quote::{format_ident, quote};
247
248    use super::{IdentPath, ModulePath};
249
250    #[test]
251    #[rustfmt::skip]
252    fn type_path() {
253        let string = IdentPath::from_ident(format_ident!("String"));
254        let my_type = IdentPath::from_parts(
255            [format_ident!("my_module")],
256            format_ident!("MyType"),
257        );
258        let serializer = IdentPath::from_parts(
259            [
260                format_ident!("my_module"),
261                format_ident!("quick_xml_serialize"),
262            ],
263            format_ident!("MyTypeSerializer"),
264        );
265        let deserializer = IdentPath::from_parts(
266            [
267                format_ident!("my_module"),
268                format_ident!("quick_xml_deserialize"),
269            ],
270            format_ident!("MyTypeDeserializer"),
271        );
272
273        let empty_path = ModulePath::default();
274        let module_path = ModulePath::default().join(format_ident!("my_module"));
275        let other_module_path = ModulePath::default().join(format_ident!("other_module"));
276        let serializer_path = module_path.clone().join(format_ident!("quick_xml_serialize"));
277        let deserializer_path = module_path.clone().join(format_ident!("quick_xml_deserialize"));
278
279        macro_rules! test {
280            ($actual:expr, $( $expected:tt )*) => {{
281                let a = $actual.to_string();
282                let b = quote!($( $expected )*).to_string();
283
284                assert_eq!(a, b);
285            }};
286        }
287
288        /* With modules */
289
290        test!(string.relative_to(&empty_path), String);
291        test!(string.relative_to(&module_path), String);
292        test!(string.relative_to(&other_module_path), String);
293        test!(string.relative_to(&serializer_path), String);
294        test!(string.relative_to(&deserializer_path), String);
295
296        test!(my_type.relative_to(&empty_path), my_module::MyType);
297        test!(my_type.relative_to(&module_path), MyType);
298        test!(my_type.relative_to(&other_module_path), super::my_module::MyType);
299        test!(my_type.relative_to(&serializer_path), super::MyType);
300        test!(my_type.relative_to(&deserializer_path), super::MyType);
301
302        test!(serializer.relative_to(&empty_path), my_module::quick_xml_serialize::MyTypeSerializer);
303        test!(serializer.relative_to(&module_path), quick_xml_serialize::MyTypeSerializer);
304        test!(serializer.relative_to(&other_module_path), super::my_module::quick_xml_serialize::MyTypeSerializer);
305        test!(serializer.relative_to(&serializer_path), MyTypeSerializer);
306        test!(serializer.relative_to(&deserializer_path), super::quick_xml_serialize::MyTypeSerializer);
307
308        test!(deserializer.relative_to(&empty_path), my_module::quick_xml_deserialize::MyTypeDeserializer);
309        test!(deserializer.relative_to(&module_path), quick_xml_deserialize::MyTypeDeserializer);
310        test!(deserializer.relative_to(&other_module_path), super::my_module::quick_xml_deserialize::MyTypeDeserializer);
311        test!(deserializer.relative_to(&serializer_path), super::quick_xml_deserialize::MyTypeDeserializer);
312        test!(deserializer.relative_to(&deserializer_path), MyTypeDeserializer);
313    }
314}