]> git.lizzy.rs Git - rust.git/blob - crates/hir_def/src/nameres/mod_resolution.rs
Replace more Name::to_string usages with Name::to_smol_str
[rust.git] / crates / hir_def / src / nameres / mod_resolution.rs
1 //! This module resolves `mod foo;` declaration to file.
2 use base_db::{AnchoredPath, FileId};
3 use hir_expand::name::Name;
4 use limit::Limit;
5 use syntax::SmolStr;
6
7 use crate::{db::DefDatabase, HirFileId};
8
9 const MOD_DEPTH_LIMIT: Limit = Limit::new(32);
10
11 #[derive(Clone, Debug)]
12 pub(super) struct ModDir {
13     /// `` for `mod.rs`, `lib.rs`
14     /// `foo/` for `foo.rs`
15     /// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs`
16     /// Invariant: path.is_empty() || path.ends_with('/')
17     dir_path: DirPath,
18     /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/`
19     root_non_dir_owner: bool,
20     depth: u32,
21 }
22
23 impl ModDir {
24     pub(super) fn root() -> ModDir {
25         ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false, depth: 0 }
26     }
27     fn child(&self, dir_path: DirPath, root_non_dir_owner: bool) -> Option<ModDir> {
28         let depth = self.depth + 1;
29         if MOD_DEPTH_LIMIT.check(depth as usize).is_err() {
30             tracing::error!("MOD_DEPTH_LIMIT exceeded");
31             cov_mark::hit!(circular_mods);
32             return None;
33         }
34         Some(ModDir { dir_path, root_non_dir_owner, depth })
35     }
36
37     pub(super) fn descend_into_definition(
38         &self,
39         name: &Name,
40         attr_path: Option<&SmolStr>,
41     ) -> Option<ModDir> {
42         let path = match attr_path.map(|it| it.as_str()) {
43             None => {
44                 let mut path = self.dir_path.clone();
45                 path.push(&name.to_smol_str());
46                 path
47             }
48             Some(attr_path) => {
49                 let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner);
50                 if !(path.is_empty() || path.ends_with('/')) {
51                     path.push('/')
52                 }
53                 DirPath::new(path)
54             }
55         };
56         self.child(path, false)
57     }
58
59     pub(super) fn resolve_declaration(
60         &self,
61         db: &dyn DefDatabase,
62         file_id: HirFileId,
63         name: &Name,
64         attr_path: Option<&SmolStr>,
65     ) -> Result<(FileId, bool, ModDir), String> {
66         let orig_file_id = file_id.original_file(db.upcast());
67
68         let mut candidate_files = Vec::new();
69         match attr_path {
70             Some(attr_path) => {
71                 candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner))
72             }
73             None => {
74                 if file_id.is_include_macro(db.upcast()) {
75                     candidate_files.push(format!("{}.rs", name));
76                     candidate_files.push(format!("{}/mod.rs", name));
77                 } else {
78                     candidate_files.push(format!("{}{}.rs", self.dir_path.0, name));
79                     candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name));
80                 }
81             }
82         };
83
84         for candidate in candidate_files.iter() {
85             let path = AnchoredPath { anchor: orig_file_id, path: candidate.as_str() };
86             if let Some(file_id) = db.resolve_path(path) {
87                 let is_mod_rs = candidate.ends_with("/mod.rs");
88
89                 let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() {
90                     (DirPath::empty(), false)
91                 } else {
92                     (DirPath::new(format!("{}/", name)), true)
93                 };
94                 if let Some(mod_dir) = self.child(dir_path, root_non_dir_owner) {
95                     return Ok((file_id, is_mod_rs, mod_dir));
96                 }
97             }
98         }
99         Err(candidate_files.remove(0))
100     }
101 }
102
103 #[derive(Clone, Debug)]
104 struct DirPath(String);
105
106 impl DirPath {
107     fn assert_invariant(&self) {
108         assert!(self.0.is_empty() || self.0.ends_with('/'));
109     }
110     fn new(repr: String) -> DirPath {
111         let res = DirPath(repr);
112         res.assert_invariant();
113         res
114     }
115     fn empty() -> DirPath {
116         DirPath::new(String::new())
117     }
118     fn push(&mut self, name: &str) {
119         self.0.push_str(name);
120         self.0.push('/');
121         self.assert_invariant();
122     }
123     fn parent(&self) -> Option<&str> {
124         if self.0.is_empty() {
125             return None;
126         };
127         let idx =
128             self.0[..self.0.len() - '/'.len_utf8()].rfind('/').map_or(0, |it| it + '/'.len_utf8());
129         Some(&self.0[..idx])
130     }
131     /// So this is the case which doesn't really work I think if we try to be
132     /// 100% platform agnostic:
133     ///
134     /// ```
135     /// mod a {
136     ///     #[path="C://sad/face"]
137     ///     mod b { mod c; }
138     /// }
139     /// ```
140     ///
141     /// Here, we need to join logical dir path to a string path from an
142     /// attribute. Ideally, we should somehow losslessly communicate the whole
143     /// construction to `FileLoader`.
144     fn join_attr(&self, mut attr: &str, relative_to_parent: bool) -> String {
145         let base = if relative_to_parent { self.parent().unwrap() } else { &self.0 };
146
147         if attr.starts_with("./") {
148             attr = &attr["./".len()..];
149         }
150         let tmp;
151         let attr = if attr.contains('\\') {
152             tmp = attr.replace('\\', "/");
153             &tmp
154         } else {
155             attr
156         };
157         let res = format!("{}{}", base, attr);
158         res
159     }
160 }