Skip to main content

split_modules/
pathfix.rs

1//! Rewrite module-relative paths inside an item that moves one level deeper.
2//!
3//! When an item moves from module `M` into `M::child`, any path that was written
4//! relative to `M` must gain one level:
5//!
6//! * `super::X`  →  `super::super::X`
7//! * `self::X`   →  `super::X`
8//!
9//! Crucially this only applies at the item's **own** module depth. A `super::` written
10//! inside a nested `mod { … }` within the item is relative to that nested module and
11//! must be left alone. We track `mod` nesting with a [`syn::visit::Visit`] and only
12//! rewrite at depth 0. Visibilities are handled separately (see [`crate::classify`]),
13//! so we skip them here to avoid double-editing. Paths hidden inside macro token
14//! streams are invisible to `syn` and therefore not rewritten — if one slips through,
15//! the compiler-verification step rolls the whole split back.
16
17use syn::visit::{self, Visit};
18
19use crate::classify::AbsVisEdit;
20
21struct PathFixer {
22    mod_depth: usize,
23    edits: Vec<AbsVisEdit>,
24}
25
26impl<'ast> Visit<'ast> for PathFixer {
27    fn visit_item_mod(&mut self, m: &'ast syn::ItemMod) {
28        self.mod_depth += 1;
29        visit::visit_item_mod(self, m);
30        self.mod_depth -= 1;
31    }
32
33    // Visibilities (`pub(in super::..)`) are rewritten by the visibility-widening pass;
34    // don't descend into them here.
35    fn visit_visibility(&mut self, _v: &'ast syn::Visibility) {}
36
37    // Don't rewrite paths that appear inside attributes.
38    fn visit_attribute(&mut self, _a: &'ast syn::Attribute) {}
39
40    // `use super::X;` / `use self::X;` statements (e.g. inside a fn body) are not
41    // `Path` nodes, so handle their leading segment explicitly.
42    fn visit_item_use(&mut self, u: &'ast syn::ItemUse) {
43        if self.mod_depth == 0 {
44            if let syn::UseTree::Path(p) = &u.tree {
45                let range = p.ident.span().byte_range();
46                match p.ident.to_string().as_str() {
47                    "super" => self.edits.push(AbsVisEdit {
48                        start: range.start,
49                        end: range.start,
50                        text: "super::".into(),
51                    }),
52                    "self" => self.edits.push(AbsVisEdit {
53                        start: range.start,
54                        end: range.end,
55                        text: "super".into(),
56                    }),
57                    _ => {}
58                }
59            }
60        }
61        visit::visit_item_use(self, u);
62    }
63
64    fn visit_path(&mut self, p: &'ast syn::Path) {
65        if self.mod_depth == 0 && p.leading_colon.is_none() && p.segments.len() >= 2 {
66            let first = &p.segments[0].ident;
67            let range = first.span().byte_range();
68            match first.to_string().as_str() {
69                "super" => self.edits.push(AbsVisEdit {
70                    start: range.start,
71                    end: range.start,
72                    text: "super::".into(),
73                }),
74                "self" => self.edits.push(AbsVisEdit {
75                    start: range.start,
76                    end: range.end,
77                    text: "super".into(),
78                }),
79                _ => {}
80            }
81        }
82        // Recurse so nested paths (generic args, etc.) are handled too.
83        visit::visit_path(self, p);
84    }
85}
86
87/// Compute the path-rewrite edits (absolute byte coords) for one moved item.
88pub fn relative_path_edits(item: &syn::Item) -> Vec<AbsVisEdit> {
89    let mut fixer = PathFixer { mod_depth: 0, edits: Vec::new() };
90    fixer.visit_item(item);
91    fixer.edits
92}