libgir/analysis/
imports.rs

1use std::{
2    borrow::Cow,
3    cmp::Ordering,
4    collections::{btree_map::BTreeMap, HashSet},
5    ops::{Deref, DerefMut},
6    vec::IntoIter,
7};
8
9use super::namespaces;
10use crate::{library::Library, nameutil::crate_name, version::Version};
11
12fn is_first_char_up(s: &str) -> bool {
13    s.chars().next().unwrap().is_uppercase()
14}
15
16fn check_up_eq(a: &str, b: &str) -> Ordering {
17    let is_a_up = is_first_char_up(a);
18    let is_b_up = is_first_char_up(b);
19    if is_a_up != is_b_up {
20        if is_a_up {
21            return Ordering::Greater;
22        }
23        return Ordering::Less;
24    }
25    Ordering::Equal
26}
27
28/// This function is used by the `Imports` type to generate output like `cargo
29/// fmt` would.
30///
31/// For example:
32///
33/// ```text
34/// use gdk; // lowercases come first.
35/// use Window;
36///
37/// use gdk::foo; // lowercases come first here as well.
38/// use gdk::Foo;
39/// ```
40fn compare_imports(a: &(&String, &ImportConditions), b: &(&String, &ImportConditions)) -> Ordering {
41    let s = check_up_eq(a.0, b.0);
42    if s != Ordering::Equal {
43        return s;
44    }
45    let mut a = a.0.split("::");
46    let mut b = b.0.split("::");
47    loop {
48        match (a.next(), b.next()) {
49            (Some(a), Some(b)) => {
50                let s = check_up_eq(a, b);
51                if s != Ordering::Equal {
52                    break s;
53                }
54                let s = a.partial_cmp(b).unwrap();
55                if s != Ordering::Equal {
56                    break s;
57                }
58            }
59            (Some(_), None) => break Ordering::Greater,
60            (None, Some(_)) => break Ordering::Less,
61            (None, None) => break Ordering::Equal,
62        }
63    }
64}
65
66/// Provides assistance in generating use declarations.
67///
68/// It takes into account that use declaration referring to names within the
69/// same crate will look differently. It also avoids generating spurious
70/// declarations referring to names from within the same module as the one we
71/// are generating code for.
72#[derive(Clone, Debug, Default)]
73pub struct Imports {
74    /// Name of the current crate.
75    crate_name: String,
76    /// Names defined within current module. It doesn't need use declaration.
77    defined: HashSet<String>,
78    defaults: ImportConditions,
79    map: BTreeMap<String, ImportConditions>,
80}
81
82impl Imports {
83    pub fn new(gir: &Library) -> Self {
84        Self {
85            crate_name: make_crate_name(gir),
86            defined: HashSet::new(),
87            defaults: ImportConditions::default(),
88            map: BTreeMap::new(),
89        }
90    }
91
92    pub fn with_defined(gir: &Library, name: &str) -> Self {
93        Self {
94            crate_name: make_crate_name(gir),
95            defined: std::iter::once(name.to_owned()).collect(),
96            defaults: ImportConditions::default(),
97            map: BTreeMap::new(),
98        }
99    }
100
101    #[must_use = "ImportsWithDefault must live while defaults are needed"]
102    pub fn with_defaults(
103        &mut self,
104        version: Option<Version>,
105        constraint: &Option<String>,
106    ) -> ImportsWithDefault<'_> {
107        let constraints = if let Some(constraint) = constraint {
108            vec![constraint.clone()]
109        } else {
110            vec![]
111        };
112        self.defaults = ImportConditions {
113            version,
114            constraints,
115        };
116
117        ImportsWithDefault::new(self)
118    }
119
120    fn reset_defaults(&mut self) {
121        self.defaults.clear();
122    }
123
124    /// The goals of this function is to discard unwanted imports like "crate".
125    /// It also extends the checks in case you are implementing "X". For
126    /// example, you don't want to import "X" or "crate::X" in this case.
127    fn common_checks(&self, name: &str) -> bool {
128        if (!name.contains("::") && name != "xlib") || self.defined.contains(name) {
129            false
130        } else if let Some(name) = name.strip_prefix("crate::") {
131            !self.defined.contains(name)
132        } else {
133            true
134        }
135    }
136
137    /// Declares that `name` is defined in scope
138    ///
139    /// Removes existing imports from `self.map` and marks `name` as
140    /// available to counter future import "requests".
141    pub fn add_defined(&mut self, name: &str) {
142        if self.defined.insert(name.to_owned()) {
143            self.map.remove(name);
144        }
145    }
146
147    /// Declares that name should be available through its last path component.
148    ///
149    /// For example, if name is `X::Y::Z` then it will be available as `Z`.
150    /// Uses defaults.
151    pub fn add(&mut self, name: &str) {
152        if !self.common_checks(name) {
153            return;
154        }
155        if let Some(mut name) = self.strip_crate_name(name) {
156            if name == "xlib" {
157                name = if self.crate_name == "gdk_x11" {
158                    // Dirty little hack to allow to have correct import for GDKX11.
159                    Cow::Borrowed("x11::xlib")
160                } else {
161                    // gtk has a module named "xlib" which is why this hack is needed too.
162                    Cow::Borrowed("crate::xlib")
163                };
164            }
165            let defaults = &self.defaults;
166            let entry = self
167                .map
168                .entry(name.into_owned())
169                .or_insert_with(|| defaults.clone());
170            entry.update_version(self.defaults.version);
171            entry.update_constraints(&self.defaults.constraints);
172        }
173    }
174
175    /// Declares that name should be available through its last path component.
176    ///
177    /// For example, if name is `X::Y::Z` then it will be available as `Z`.
178    pub fn add_with_version(&mut self, name: &str, version: Option<Version>) {
179        if !self.common_checks(name) {
180            return;
181        }
182        if let Some(name) = self.strip_crate_name(name) {
183            let entry = self
184                .map
185                .entry(name.into_owned())
186                .or_insert(ImportConditions {
187                    version,
188                    constraints: Vec::new(),
189                });
190            entry.update_version(version);
191            // Since there is no constraint on this import, if any constraint
192            // is present, we can just remove it.
193            entry.constraints.clear();
194        }
195    }
196
197    /// Declares that name should be available through its last path component
198    /// and provides an optional feature constraint.
199    ///
200    /// For example, if name is `X::Y::Z` then it will be available as `Z`.
201    pub fn add_with_constraint(
202        &mut self,
203        name: &str,
204        version: Option<Version>,
205        constraint: Option<&str>,
206    ) {
207        if !self.common_checks(name) {
208            return;
209        }
210        if let Some(name) = self.strip_crate_name(name) {
211            let entry = if let Some(constraint) = constraint {
212                let constraint = String::from(constraint);
213                let entry = self
214                    .map
215                    .entry(name.into_owned())
216                    .or_insert(ImportConditions {
217                        version,
218                        constraints: vec![constraint.clone()],
219                    });
220                entry.add_constraint(constraint);
221                entry
222            } else {
223                let entry = self
224                    .map
225                    .entry(name.into_owned())
226                    .or_insert(ImportConditions {
227                        version,
228                        constraints: Vec::new(),
229                    });
230                // Since there is no constraint on this import, if any constraint
231                // is present, we can just remove it.
232                entry.constraints.clear();
233                entry
234            };
235            entry.update_version(version);
236        }
237    }
238
239    /// Declares that name should be available through its full path.
240    ///
241    /// For example, if name is `X::Y` then it will be available as `X::Y`.
242    pub fn add_used_type(&mut self, used_type: &str) {
243        if let Some(i) = used_type.find("::") {
244            if i == 0 {
245                self.add(&used_type[2..]);
246            } else {
247                self.add(&used_type[..i]);
248            }
249        } else {
250            self.add(&format!("crate::{used_type}"));
251        }
252    }
253
254    pub fn add_used_types(&mut self, used_types: &[String]) {
255        for s in used_types {
256            self.add_used_type(s);
257        }
258    }
259
260    /// Declares that name should be available through its full path.
261    ///
262    /// For example, if name is `X::Y` then it will be available as `X::Y`.
263    pub fn add_used_type_with_version(&mut self, used_type: &str, version: Option<Version>) {
264        if let Some(i) = used_type.find("::") {
265            if i == 0 {
266                self.add_with_version(&used_type[2..], version);
267            } else {
268                self.add_with_version(&used_type[..i], version);
269            }
270        } else {
271            self.add_with_version(&format!("crate::{used_type}"), version);
272        }
273    }
274
275    /// Tries to strip crate name prefix from given name.
276    ///
277    /// Returns `None` if name matches crate name exactly. Otherwise returns
278    /// name with crate name prefix stripped or full name if there was no match.
279    fn strip_crate_name<'a>(&self, name: &'a str) -> Option<Cow<'a, str>> {
280        let prefix = &self.crate_name;
281        if !name.starts_with(prefix) {
282            return Some(Cow::Borrowed(name));
283        }
284        let rest = &name[prefix.len()..];
285        if rest.is_empty() {
286            None
287        } else if rest.starts_with("::") {
288            Some(Cow::Owned(format!("crate{rest}")))
289        } else {
290            // It was false positive, return the whole name.
291            Some(Cow::Borrowed(name))
292        }
293    }
294
295    pub fn iter(&self) -> IntoIter<(&String, &ImportConditions)> {
296        let mut imports = self.map.iter().collect::<Vec<_>>();
297        imports.sort_by(compare_imports);
298        imports.into_iter()
299    }
300}
301
302pub struct ImportsWithDefault<'a> {
303    imports: &'a mut Imports,
304}
305
306impl<'a> ImportsWithDefault<'a> {
307    fn new(imports: &'a mut Imports) -> Self {
308        Self { imports }
309    }
310}
311
312impl Drop for ImportsWithDefault<'_> {
313    fn drop(&mut self) {
314        self.imports.reset_defaults();
315    }
316}
317
318impl Deref for ImportsWithDefault<'_> {
319    type Target = Imports;
320    fn deref(&self) -> &Self::Target {
321        self.imports
322    }
323}
324
325impl DerefMut for ImportsWithDefault<'_> {
326    fn deref_mut(&mut self) -> &mut Self::Target {
327        self.imports
328    }
329}
330
331#[derive(Clone, Debug, Default, Ord, PartialEq, PartialOrd, Eq)]
332pub struct ImportConditions {
333    pub version: Option<Version>,
334    pub constraints: Vec<String>,
335}
336
337impl ImportConditions {
338    fn clear(&mut self) {
339        self.version = None;
340        self.constraints.clear();
341    }
342
343    fn update_version(&mut self, version: Option<Version>) {
344        if version < self.version {
345            self.version = version;
346        }
347    }
348
349    fn add_constraint(&mut self, constraint: String) {
350        // If the import is already present but doesn't have any constraint,
351        // we don't want to add one.
352        if self.constraints.is_empty() {
353            return;
354        }
355        // Otherwise, we just check if the constraint
356        // is already present or not before adding it.
357        if !self.constraints.iter().any(|x| x == &constraint) {
358            self.constraints.push(constraint);
359        }
360    }
361
362    fn update_constraints(&mut self, constraints: &[String]) {
363        // If the import is already present but doesn't have any constraint,
364        // we don't want to add one.
365        if self.constraints.is_empty() {
366            return;
367        }
368        if constraints.is_empty() {
369            // Since there is no constraint on this import, if any constraint
370            // is present, we can just remove it.
371            self.constraints.clear();
372        } else {
373            // Otherwise, we just check if the constraint
374            // is already present or not before adding it.
375            for constraint in constraints {
376                if !self.constraints.iter().any(|x| x == constraint) {
377                    self.constraints.push(constraint.clone());
378                }
379            }
380        }
381    }
382}
383
384fn make_crate_name(gir: &Library) -> String {
385    if gir.is_glib_crate() {
386        crate_name("GLib")
387    } else {
388        crate_name(gir.namespace(namespaces::MAIN).name.as_str())
389    }
390}