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]
64 pub fn find_by_name(&self, name: &str) -> Vec<ItemId> {
65 if let Some(ids) = self.name_index.get(&Arc::<str>::from(name)) {
66 return ids.clone();
67 }
68 let key: Arc<str> = {
71 let pool =
72 self.graph.string_pool.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
73 if let Some(a) = pool.get(name) {
74 a.clone()
75 } else {
76 Arc::from(name)
77 }
78 };
79 self.name_index.get(&key).cloned().unwrap_or_default()
80 }
81
82 #[must_use]
84 pub fn item_path(&self, id: &ItemId) -> Option<&PathBuf> {
85 self.item_to_file.get(id)
86 }
87
88 #[must_use]
90 pub fn item_kind(&self, id: &ItemId) -> Option<ItemType> {
91 let path = self.item_to_file.get(id)?;
92 let file = self.graph.files.get(path)?;
93 for it in &file.items {
94 if &it.id == id {
95 return Some(it.item_type.clone());
96 }
97 }
98 None
99 }
100
101 #[must_use]
102 pub fn new(graph: &'a KnowledgeGraph) -> Self {
103 let files_len = graph.files.len();
105 let mut approx_items = 0usize;
106 let mut approx_imports = 0usize;
107 for f in graph.files.values() {
108 approx_items += f.items.len();
109 approx_imports += f.imports.len();
110 }
111
112 let intern_str = |s: &str| -> Arc<str> {
114 let mut pool =
115 graph.string_pool.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
116 if let Some(a) = pool.get(s) {
117 return a.clone();
118 }
119 let a: Arc<str> = Arc::from(s);
120 pool.insert(s.to_string(), a.clone());
121 a
122 };
123
124 let mut name_index: HashMap<Arc<str>, Vec<ItemId>> =
125 HashMap::with_capacity(approx_items * 2);
126 let mut module_index: HashMap<Arc<str>, ItemId> =
127 HashMap::with_capacity(files_len.saturating_mul(2));
128 let mut item_to_file: HashMap<ItemId, PathBuf> = HashMap::with_capacity(approx_items);
129 let mut alias_map: HashMap<Arc<str>, Vec<Arc<str>>> =
130 HashMap::with_capacity(approx_imports);
131 let mut exposure_map: HashMap<PathBuf, HashMap<Arc<str>, Vec<Arc<str>>>> =
132 HashMap::with_capacity(files_len);
133
134 for (path, file) in &graph.files {
135 if !file.imports.is_empty() {
137 exposure_map
138 .entry(path.clone())
139 .or_insert_with(|| HashMap::with_capacity(file.imports.len()));
140 }
141 for (idx, it) in file.items.iter().enumerate() {
142 item_to_file.insert(it.id.clone(), path.clone());
143 let nm = intern_str(it.name.as_ref());
144 name_index.entry(nm).or_default().push(it.id.clone());
145 if idx == 0 {
146 if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
147 let st = intern_str(stem);
148 module_index.insert(st, it.id.clone());
149 }
150 }
151 }
152 if let Some(pre) = graph.import_segments.get(path) {
154 for (segments, alias_arc) in pre {
155 if segments.is_empty() {
156 continue;
157 }
158 if let Some(k) = alias_arc.clone() {
159 alias_map.insert(k, segments.clone());
160 } else if let Some(last) = segments.last().cloned() {
161 exposure_map
162 .entry(path.clone())
163 .or_default()
164 .insert(last, segments.clone());
165 }
166 }
167 } else {
168 for imp in &file.imports {
169 let segments: Vec<Arc<str>> =
170 imp.path.split("::").filter(|s| !s.is_empty()).map(intern_str).collect();
171 if let Some(alias) = &imp.alias {
172 if alias.as_ref() == "_" {
174 continue;
175 }
176 if !alias.is_empty() && !segments.is_empty() {
177 let k = intern_str(alias.as_ref());
178 alias_map.insert(k, segments);
179 }
180 } else if let Some(last) = segments.last().cloned() {
181 exposure_map.entry(path.clone()).or_default().insert(last, segments);
183 }
184 }
185 }
186 }
187 Self { graph, name_index, module_index, item_to_file, alias_map, exposure_map }
188 }
189
190 pub fn resolve_import(&self, from_file: &Path, raw_path: &str) -> Vec<ItemId> {
193 let path = raw_path.split(" as ").next().unwrap_or(raw_path).trim();
195 let mut parts: Vec<Arc<str>> =
196 path.split("::").filter(|s| !s.is_empty()).map(Arc::<str>::from).collect();
197 if parts.is_empty() {
198 return Vec::new();
199 }
200
201 let mut scope: Vec<String> = self.module_segments_for(from_file);
203 loop {
204 match parts.first().map(std::convert::AsRef::as_ref) {
205 Some("crate") => {
206 parts.remove(0);
207 scope.clear();
208 }
209 Some("self") => {
210 parts.remove(0); }
212 Some("super") => {
213 parts.remove(0);
214 if !scope.is_empty() {
215 scope.pop();
216 }
217 }
218 _ => break,
219 }
220 }
221 if parts.is_empty() {
222 return Vec::new();
223 }
224
225 if let Some(first) = parts.first().cloned() {
227 if let Some(mapped) = self.alias_map.get(&first) {
228 parts.remove(0);
229 let mut new_parts = mapped.clone();
230 new_parts.extend(parts);
231 parts = new_parts;
232 }
233 }
234
235 if let Some(first) = parts.first().cloned() {
237 if let Some(map) = self.exposure_map.get(from_file) {
238 if let Some(mapped) = map.get(&first) {
239 parts.remove(0);
240 let mut new_parts = mapped.clone();
241 new_parts.extend(parts);
242 parts = new_parts;
243 }
244 }
245 }
246
247 let parts_str: Vec<&str> = parts.iter().map(Arc::<str>::as_ref).collect();
250 if let Some(ids) = self.resolve_scoped_chain(from_file, &scope, &parts_str) {
251 return ids;
252 }
253
254 let Some(last) = parts.last() else {
256 return Vec::new();
257 };
258 if let Some(ids) = self.name_index.get(last) {
259 return ids.clone();
260 }
261
262 if let Some(mid) = self.module_index.get(last) {
264 return vec![mid.clone()];
265 }
266
267 if parts.len() >= 2 {
269 let first = parts[0].as_ref();
270 if let Some(_m0) = self.module_index.get(first) {
271 if let Some(ids) = self.name_index.get(last) {
272 return ids.clone();
273 }
274 }
275 if let Some(scope_head) = scope.first() {
277 if let Some(_m) = self.module_index.get(scope_head.as_str()) {
278 if let Some(ids) = self.name_index.get(last) {
279 return ids.clone();
280 }
281 }
282 }
283 }
284
285 Vec::new()
286 }
287
288 #[must_use]
289 pub fn is_item_function(&self, id: &ItemId) -> bool {
290 if let Some(file) = self.item_to_file.get(id).and_then(|p| self.graph.files.get(p)) {
291 if let Some(Item { item_type, .. }) = file.items.iter().find(|it| &it.id == id) {
292 return matches!(item_type, ItemType::Function { .. });
293 }
294 }
295 false
296 }
297
298 #[must_use]
299 pub fn is_file_level_module(&self, id: &ItemId) -> bool {
300 if let Some(file_path) = self.item_to_file.get(id) {
301 if let Some(file) = self.graph.files.get(file_path) {
302 if let Some(first) = file.items.first() {
303 return &first.id == id;
304 }
305 }
306 }
307 false
308 }
309
310 fn resolve_scoped_chain(
313 &self,
314 from_file: &Path,
315 scope: &[String],
316 parts: &[&str],
317 ) -> Option<Vec<ItemId>> {
318 if parts.is_empty() {
319 return None;
320 }
321 let (base_src, _src_idx) = Self::base_src_dir(from_file)?;
322 let mut dir = base_src.clone();
324 let mut scope_dirs: Vec<&str> = scope.iter().map(std::string::String::as_str).collect();
325 let is_leaf = from_file
327 .file_name()
328 .and_then(|s| s.to_str())
329 .is_some_and(|f| f != "mod.rs" && f != "lib.rs");
330 if is_leaf && !scope_dirs.is_empty() {
331 scope_dirs.pop();
332 }
333 for seg in scope_dirs {
334 dir.push(seg);
335 }
336 for seg in &parts[..parts.len().saturating_sub(1)] {
338 dir.push(seg);
340 let has_mod = self.graph.files.contains_key(&dir.join("mod.rs"));
342 let has_lib = !has_mod && self.graph.files.contains_key(&dir.join("lib.rs"));
343 let found_dir = has_mod || has_lib;
344 if !found_dir {
345 dir.pop();
347 let file_rs = dir.join(format!("{seg}.rs"));
348 if self.graph.files.contains_key(&file_rs) {
349 dir.push(seg);
351 } else {
352 return None;
353 }
354 }
355 }
356 let last = parts[parts.len() - 1];
358 let file_rs = dir.join(format!("{last}.rs"));
360 if let Some(fnode) = self.graph.files.get(&file_rs) {
361 let mut ids: Vec<ItemId> = Vec::with_capacity(fnode.items.len());
363 for it in &fnode.items {
364 if it.name.as_ref() == last {
365 ids.push(it.id.clone());
366 }
367 }
368 if !ids.is_empty() {
369 return Some(ids);
370 }
371 if let Some(mid) = self.module_index.get(last) {
373 return Some(vec![mid.clone()]);
374 }
375 }
376 let mod_path = dir.join("mod.rs");
378 let lib_path = dir.join("lib.rs");
379 for cand in [mod_path, lib_path] {
380 if let Some(fnode) = self.graph.files.get(&cand) {
381 let mut ids: Vec<ItemId> = Vec::with_capacity(fnode.items.len());
382 for it in &fnode.items {
383 if it.name.as_ref() == last {
384 ids.push(it.id.clone());
385 }
386 }
387 if !ids.is_empty() {
388 return Some(ids);
389 }
390 }
391 }
392 None
393 }
394
395 fn base_src_dir(path: &Path) -> Option<(PathBuf, usize)> {
397 let comps: Vec<_> = path.components().collect();
398 let mut src_idx: Option<usize> = None;
399 for (i, c) in comps.iter().enumerate() {
400 if let std::path::Component::Normal(os) = c {
401 if os.to_str() == Some("src") {
402 src_idx = Some(i);
403 break;
404 }
405 }
406 }
407 let i = src_idx?;
408 let mut base = PathBuf::new();
409 for c in &comps[..=i] {
410 base.push(c.as_os_str());
411 }
412 Some((base, i))
413 }
414}