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}