rust_relations_explorer/graph/
resolver.rs1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use crate::graph::{Item, ItemId, ItemType, KnowledgeGraph};
6
7pub struct Resolver<'a> {
8 graph: &'a KnowledgeGraph,
9 name_index: HashMap<Arc<str>, Vec<ItemId>>,
11 module_index: HashMap<Arc<str>, ItemId>,
13 item_to_file: HashMap<ItemId, PathBuf>,
15 alias_map: HashMap<Arc<str>, Vec<Arc<str>>>,
17 exposure_map: HashMap<PathBuf, HashMap<Arc<str>, Vec<Arc<str>>>>,
19}
20
21impl Resolver<'_> {
22 fn module_segments_for(&self, path: &Path) -> Vec<String> {
24 if let Some(segs) = self.graph.module_segments.get(path) {
26 return segs.clone();
27 }
28 let mut src_idx: Option<usize> = None;
30 let comps: Vec<_> = path.components().collect();
31 for (i, c) in comps.iter().enumerate() {
32 if let std::path::Component::Normal(os) = c {
33 if os.to_str() == Some("src") {
34 src_idx = Some(i);
35 break;
36 }
37 }
38 }
39 let mut segs: Vec<String> = Vec::new();
40 if let Some(i) = src_idx {
41 for c in &comps[i + 1..comps.len().saturating_sub(1)] {
42 if let std::path::Component::Normal(os) = c {
43 if let Some(s) = os.to_str() {
44 segs.push(s.to_string());
45 }
46 }
47 }
48 if let Some(file_os) = path.file_name() {
49 let file = file_os.to_string_lossy();
50 if file != "mod.rs" && file != "lib.rs" {
51 if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
52 segs.push(stem.to_string());
53 }
54 }
55 }
56 }
57 segs
58 }
59}
60
61impl<'a> Resolver<'a> {
62 #[must_use]
63 pub fn new(graph: &'a KnowledgeGraph) -> Self {
64 let files_len = graph.files.len();
66 let mut approx_items = 0usize;
67 let mut approx_imports = 0usize;
68 for f in graph.files.values() {
69 approx_items += f.items.len();
70 approx_imports += f.imports.len();
71 }
72
73 let intern_str = |s: &str| -> Arc<str> {
75 let mut pool =
76 graph.string_pool.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
77 if let Some(a) = pool.get(s) {
78 return a.clone();
79 }
80 let a: Arc<str> = Arc::from(s);
81 pool.insert(s.to_string(), a.clone());
82 a
83 };
84
85 let mut name_index: HashMap<Arc<str>, Vec<ItemId>> =
86 HashMap::with_capacity(approx_items * 2);
87 let mut module_index: HashMap<Arc<str>, ItemId> =
88 HashMap::with_capacity(files_len.saturating_mul(2));
89 let mut item_to_file: HashMap<ItemId, PathBuf> = HashMap::with_capacity(approx_items);
90 let mut alias_map: HashMap<Arc<str>, Vec<Arc<str>>> =
91 HashMap::with_capacity(approx_imports);
92 let mut exposure_map: HashMap<PathBuf, HashMap<Arc<str>, Vec<Arc<str>>>> =
93 HashMap::with_capacity(files_len);
94
95 for (path, file) in &graph.files {
96 if !file.imports.is_empty() {
98 exposure_map
99 .entry(path.clone())
100 .or_insert_with(|| HashMap::with_capacity(file.imports.len()));
101 }
102 for (idx, it) in file.items.iter().enumerate() {
103 item_to_file.insert(it.id.clone(), path.clone());
104 let nm = intern_str(it.name.as_ref());
105 name_index.entry(nm).or_default().push(it.id.clone());
106 if idx == 0 {
107 if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
108 let st = intern_str(stem);
109 module_index.insert(st, it.id.clone());
110 }
111 }
112 }
113 if let Some(pre) = graph.import_segments.get(path) {
115 for (segments, alias_arc) in pre {
116 if segments.is_empty() {
117 continue;
118 }
119 if let Some(k) = alias_arc.clone() {
120 alias_map.insert(k, segments.clone());
121 } else if let Some(last) = segments.last().cloned() {
122 exposure_map
123 .entry(path.clone())
124 .or_default()
125 .insert(last, segments.clone());
126 }
127 }
128 } else {
129 for imp in &file.imports {
130 let segments: Vec<Arc<str>> =
131 imp.path.split("::").filter(|s| !s.is_empty()).map(intern_str).collect();
132 if let Some(alias) = &imp.alias {
133 if alias.as_ref() == "_" {
135 continue;
136 }
137 if !alias.is_empty() && !segments.is_empty() {
138 let k = intern_str(alias.as_ref());
139 alias_map.insert(k, segments);
140 }
141 } else if let Some(last) = segments.last().cloned() {
142 exposure_map.entry(path.clone()).or_default().insert(last, segments);
144 }
145 }
146 }
147 }
148 Self { graph, name_index, module_index, item_to_file, alias_map, exposure_map }
149 }
150
151 pub fn resolve_import(&self, from_file: &Path, raw_path: &str) -> Vec<ItemId> {
154 let path = raw_path.split(" as ").next().unwrap_or(raw_path).trim();
156 let mut parts: Vec<Arc<str>> =
157 path.split("::").filter(|s| !s.is_empty()).map(Arc::<str>::from).collect();
158 if parts.is_empty() {
159 return Vec::new();
160 }
161
162 let mut scope: Vec<String> = self.module_segments_for(from_file);
164 loop {
165 match parts.first().map(std::convert::AsRef::as_ref) {
166 Some("crate") => {
167 parts.remove(0);
168 scope.clear();
169 }
170 Some("self") => {
171 parts.remove(0); }
173 Some("super") => {
174 parts.remove(0);
175 if !scope.is_empty() {
176 scope.pop();
177 }
178 }
179 _ => break,
180 }
181 }
182 if parts.is_empty() {
183 return Vec::new();
184 }
185
186 if let Some(first) = parts.first().cloned() {
188 if let Some(mapped) = self.alias_map.get(&first) {
189 parts.remove(0);
190 let mut new_parts = mapped.clone();
191 new_parts.extend(parts);
192 parts = new_parts;
193 }
194 }
195
196 if let Some(first) = parts.first().cloned() {
198 if let Some(map) = self.exposure_map.get(from_file) {
199 if let Some(mapped) = map.get(&first) {
200 parts.remove(0);
201 let mut new_parts = mapped.clone();
202 new_parts.extend(parts);
203 parts = new_parts;
204 }
205 }
206 }
207
208 let parts_str: Vec<&str> = parts.iter().map(Arc::<str>::as_ref).collect();
211 if let Some(ids) = self.resolve_scoped_chain(from_file, &scope, &parts_str) {
212 return ids;
213 }
214
215 let Some(last) = parts.last() else {
217 return Vec::new();
218 };
219 if let Some(ids) = self.name_index.get(last) {
220 return ids.clone();
221 }
222
223 if let Some(mid) = self.module_index.get(last) {
225 return vec![mid.clone()];
226 }
227
228 if parts.len() >= 2 {
230 let first = parts[0].as_ref();
231 if let Some(_m0) = self.module_index.get(first) {
232 if let Some(ids) = self.name_index.get(last) {
233 return ids.clone();
234 }
235 }
236 if let Some(scope_head) = scope.first() {
238 if let Some(_m) = self.module_index.get(scope_head.as_str()) {
239 if let Some(ids) = self.name_index.get(last) {
240 return ids.clone();
241 }
242 }
243 }
244 }
245
246 Vec::new()
247 }
248
249 #[must_use]
250 pub fn is_item_function(&self, id: &ItemId) -> bool {
251 if let Some(file) = self.item_to_file.get(id).and_then(|p| self.graph.files.get(p)) {
252 if let Some(Item { item_type, .. }) = file.items.iter().find(|it| &it.id == id) {
253 return matches!(item_type, ItemType::Function { .. });
254 }
255 }
256 false
257 }
258
259 #[must_use]
260 pub fn is_file_level_module(&self, id: &ItemId) -> bool {
261 if let Some(file_path) = self.item_to_file.get(id) {
262 if let Some(file) = self.graph.files.get(file_path) {
263 if let Some(first) = file.items.first() {
264 return &first.id == id;
265 }
266 }
267 }
268 false
269 }
270
271 fn resolve_scoped_chain(
274 &self,
275 from_file: &Path,
276 scope: &[String],
277 parts: &[&str],
278 ) -> Option<Vec<ItemId>> {
279 if parts.is_empty() {
280 return None;
281 }
282 let (base_src, _src_idx) = Self::base_src_dir(from_file)?;
283 let mut dir = base_src.clone();
285 let mut scope_dirs: Vec<&str> = scope.iter().map(std::string::String::as_str).collect();
286 let is_leaf = from_file
288 .file_name()
289 .and_then(|s| s.to_str())
290 .is_some_and(|f| f != "mod.rs" && f != "lib.rs");
291 if is_leaf && !scope_dirs.is_empty() {
292 scope_dirs.pop();
293 }
294 for seg in scope_dirs {
295 dir.push(seg);
296 }
297 for seg in &parts[..parts.len().saturating_sub(1)] {
299 dir.push(seg);
301 let has_mod = self.graph.files.contains_key(&dir.join("mod.rs"));
303 let has_lib = !has_mod && self.graph.files.contains_key(&dir.join("lib.rs"));
304 let found_dir = has_mod || has_lib;
305 if !found_dir {
306 dir.pop();
308 let file_rs = dir.join(format!("{seg}.rs"));
309 if self.graph.files.contains_key(&file_rs) {
310 dir.push(seg);
312 } else {
313 return None;
314 }
315 }
316 }
317 let last = parts[parts.len() - 1];
319 let file_rs = dir.join(format!("{last}.rs"));
321 if let Some(fnode) = self.graph.files.get(&file_rs) {
322 let mut ids: Vec<ItemId> = Vec::with_capacity(fnode.items.len());
324 for it in &fnode.items {
325 if it.name.as_ref() == last {
326 ids.push(it.id.clone());
327 }
328 }
329 if !ids.is_empty() {
330 return Some(ids);
331 }
332 if let Some(mid) = self.module_index.get(last) {
334 return Some(vec![mid.clone()]);
335 }
336 }
337 let mod_path = dir.join("mod.rs");
339 let lib_path = dir.join("lib.rs");
340 for cand in [mod_path, lib_path] {
341 if let Some(fnode) = self.graph.files.get(&cand) {
342 let mut ids: Vec<ItemId> = Vec::with_capacity(fnode.items.len());
343 for it in &fnode.items {
344 if it.name.as_ref() == last {
345 ids.push(it.id.clone());
346 }
347 }
348 if !ids.is_empty() {
349 return Some(ids);
350 }
351 }
352 }
353 None
354 }
355
356 fn base_src_dir(path: &Path) -> Option<(PathBuf, usize)> {
358 let comps: Vec<_> = path.components().collect();
359 let mut src_idx: Option<usize> = None;
360 for (i, c) in comps.iter().enumerate() {
361 if let std::path::Component::Normal(os) = c {
362 if os.to_str() == Some("src") {
363 src_idx = Some(i);
364 break;
365 }
366 }
367 }
368 let i = src_idx?;
369 let mut base = PathBuf::new();
370 for c in &comps[..=i] {
371 base.push(c.as_os_str());
372 }
373 Some((base, i))
374 }
375}