bpaf_derive/
custom_path.rs

1use syn::{
2    punctuated::Punctuated,
3    token::{self, PathSep},
4    visit_mut::{self, VisitMut},
5    PathSegment, UseName, UsePath, UseRename, UseTree,
6};
7
8/// Implements [`syn::visit_mut::VisitMut`] to find
9/// those [`Path`](syn::Path)s which match
10/// [`query`](Self::target) and replace them with [`target`](Self::target).
11pub(crate) struct CratePathReplacer {
12    /// The prefix to search for within an input path.
13    query: syn::Path,
14    /// The prefix we wish the input path to have.
15    target: syn::Path,
16}
17
18impl CratePathReplacer {
19    pub(crate) fn new(target: syn::Path, replacement: syn::Path) -> Self {
20        CratePathReplacer {
21            query: target,
22            target: replacement,
23        }
24    }
25
26    /// Check if both [`query`](Self::query) and `input` have the same leading
27    /// path segment (`::`) responsible for marking [a path as
28    /// "global"](https://doc.rust-lang.org/reference/procedural-macros.html#procedural-macro-hygiene).
29    ///
30    /// If these do not match, no replacement will be performed.
31    fn path_global_match(&self, input: &mut syn::Path) -> bool {
32        self.query.leading_colon.is_some() && input.leading_colon.is_some()
33    }
34
35    /// Check if the initial segments of `input` match [`query`](Self::query).
36    ///
37    /// If these do not match, no replacement will be performed.
38    fn path_segments_match(&self, input: &mut syn::Path) -> bool {
39        self.query
40            .segments
41            .iter()
42            .zip(input.segments.iter())
43            .all(|(f, o)| f == o)
44    }
45
46    /// Replaces the prefix of `input` with those of [`target`](Self::target) if
47    /// the `input` path's prefix matches [`query`](Self::query).
48    fn replace_path_if_match(&self, input: &mut syn::Path) {
49        if self.path_global_match(input) && self.path_segments_match(input) {
50            input.leading_colon = self.target.leading_colon;
51            input.segments = self
52                .target
53                .segments
54                .clone()
55                .into_iter()
56                .chain(
57                    input
58                        .segments
59                        .iter()
60                        .skip(self.query.segments.iter().count())
61                        .cloned(),
62                )
63                .collect::<Punctuated<_, _>>();
64        }
65    }
66
67    fn item_use_global_match(&self, input: &syn::ItemUse) -> bool {
68        self.query.leading_colon == input.leading_colon
69    }
70
71    fn item_use_segments_match<'a, Q: Iterator<Item = &'a PathSegment>>(
72        input: &'a UseTree,
73        query_len: usize,
74        mut query_iter: Q,
75        mut matched_parts: Vec<&'a UseTree>,
76    ) -> Option<(Vec<&'a UseTree>, Option<UseTree>)> {
77        if let Some(next_to_match) = query_iter.next() {
78            match input {
79                UseTree::Path(path) => {
80                    if next_to_match.ident == path.ident {
81                        matched_parts.push(input);
82                        return Self::item_use_segments_match(
83                            path.tree.as_ref(),
84                            query_len,
85                            query_iter,
86                            matched_parts,
87                        );
88                    }
89                }
90                UseTree::Name(name) => {
91                    if next_to_match.ident == name.ident {
92                        if query_iter.next().is_some() {
93                            return None;
94                        } else {
95                            matched_parts.push(input);
96                        }
97                    }
98                }
99                UseTree::Rename(rename) => {
100                    if next_to_match.ident == rename.ident {
101                        if query_iter.next().is_some() {
102                            return None;
103                        } else {
104                            matched_parts.push(input);
105                        }
106                    }
107                }
108                UseTree::Glob(_) => {}
109                UseTree::Group(_) => {}
110            }
111        }
112
113        if query_len == matched_parts.len() {
114            Some((matched_parts, Some(input.clone())))
115        } else {
116            None
117        }
118    }
119
120    fn append_suffix_to_target(
121        &self,
122        matched_parts: Vec<&UseTree>,
123        suffix: Option<UseTree>,
124    ) -> UseTree {
125        let last_input_match = matched_parts
126            .last()
127            .expect("If a match exists, then it the matched prefix must be non-empty.");
128        let mut rev_target_ids = self.target.segments.iter().map(|s| s.ident.clone()).rev();
129        let mut result_tree = match last_input_match {
130            UseTree::Path(_) => {
131                if let Some(suffix_tree) = suffix {
132                    UseTree::Path(UsePath {
133                        ident: rev_target_ids.next().expect(
134                            "error while making a `UseTree::Path`: target should not be empty",
135                        ),
136                        colon2_token: PathSep::default(),
137                        tree: Box::new(suffix_tree),
138                    })
139                } else {
140                    unreachable!("If the last part of the matched input was a path, then there must be some suffix left to attach to complete it.")
141                }
142            }
143            UseTree::Name(_) => {
144                assert!(suffix.is_none(), "If the last part of the matched input was a syn::UseTree::Name, then there shouldn't be any suffix left to attach to the prefix.");
145                UseTree::Name(UseName {
146                    ident: rev_target_ids
147                        .next()
148                        .expect("error while making a `UseTree::Name`: target should not be empty"),
149                })
150            }
151            UseTree::Rename(original_rename) => {
152                assert!(suffix.is_none(), "If the last part of the matched input was a syn::UseTree::Rename, then there shouldn't be any suffix left to attach to the prefix.");
153                UseTree::Rename(UseRename {
154                    ident: rev_target_ids.next().expect(
155                        "error while making a `UseTree::Rename`: target should not be empty",
156                    ),
157                    as_token: token::As::default(),
158                    rename: original_rename.rename.clone(),
159                })
160            }
161            UseTree::Glob(_) => unreachable!(
162                "There is no functionality for matching against a syn::UseTree::Group."
163            ),
164            UseTree::Group(_) => unreachable!(
165                "There is no functionality for matching against a syn::UseTree::Group."
166            ),
167        };
168        for id in rev_target_ids {
169            result_tree = UseTree::Path(UsePath {
170                ident: id,
171                colon2_token: PathSep::default(),
172                tree: Box::new(result_tree),
173            })
174        }
175        result_tree
176    }
177
178    /// Replaces the prefix of `input` with those of [`target`](Self::target) if
179    /// the `input` path's prefix matches [`query`](Self::query).
180    fn replace_item_use_if_match(&self, input: &mut syn::ItemUse) {
181        if self.item_use_global_match(input) {
182            if let Some((matched_prefix, suffix)) = Self::item_use_segments_match(
183                &input.tree,
184                self.query.segments.len(),
185                self.query.segments.iter(),
186                vec![],
187            ) {
188                input.leading_colon = self.target.leading_colon;
189                input.tree = self.append_suffix_to_target(matched_prefix, suffix);
190            }
191        }
192    }
193}
194
195impl VisitMut for CratePathReplacer {
196    fn visit_path_mut(&mut self, path: &mut syn::Path) {
197        self.replace_path_if_match(path);
198        visit_mut::visit_path_mut(self, path);
199    }
200
201    fn visit_item_use_mut(&mut self, item_use: &mut syn::ItemUse) {
202        self.replace_item_use_if_match(item_use);
203        visit_mut::visit_item_use_mut(self, item_use);
204    }
205}