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