1use crate::doc_ref::ParentRef;
37use crate::iterators::{LazyChild, LazyChildren};
38use crate::string_utils::case_aware_jaro_winkler;
39use crate::{DocRef, Navigator, RustdocData};
40use fieldwork::Fieldwork;
41use rustdoc_types::{Id, Item, ItemEnum, ItemKind, Use};
42use semver::VersionReq;
43
44#[derive(Fieldwork)]
47#[fieldwork(get)]
48pub struct Suggestion<'a> {
49 path: String,
50 item: Option<DocRef<'a, Item>>,
51 score: f64,
52}
53
54#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
62pub(crate) struct ItemKey {
63 crate_docs: usize,
64 item: usize,
65}
66
67impl<'a, T> From<DocRef<'a, T>> for ItemKey {
68 fn from(d: DocRef<'a, T>) -> Self {
69 Self {
70 crate_docs: d.crate_docs() as *const RustdocData as usize,
71 item: d.item() as *const T as usize,
72 }
73 }
74}
75
76#[derive(Clone, PartialEq, Eq, Debug)]
77struct Frame {
78 item: ItemKey,
79 segment: Option<String>,
83}
84
85pub struct Resolver<'a> {
95 navigator: &'a Navigator,
96 stack: Vec<Frame>,
97}
98
99impl<'a> Resolver<'a> {
100 pub fn new(navigator: &'a Navigator) -> Self {
101 Self {
102 navigator,
103 stack: Vec::new(),
104 }
105 }
106
107 pub fn navigator(&self) -> &'a Navigator {
108 self.navigator
109 }
110
111 pub fn current_path(&self) -> impl Iterator<Item = &str> + '_ {
114 self.stack.iter().filter_map(|f| f.segment.as_deref())
115 }
116
117 fn with_pushed<R>(
120 &mut self,
121 item: ItemKey,
122 segment: Option<&str>,
123 f: impl FnOnce(&mut Self) -> R,
124 ) -> Option<R> {
125 let frame = Frame {
126 item,
127 segment: segment.map(str::to_owned),
128 };
129 if self.stack.contains(&frame) {
130 log::trace!("cycle: {frame:?}");
131 return None;
132 }
133 self.stack.push(frame);
134 let r = f(self);
135 self.stack.pop();
136 Some(r)
137 }
138}
139
140impl<'a> Resolver<'a> {
143 pub fn resolve_path(
147 &mut self,
148 mut path: &str,
149 suggestions: &mut Vec<Suggestion<'a>>,
150 ) -> Option<DocRef<'a, Item>> {
151 if let Some(p) = path.strip_prefix("::") {
152 path = p;
153 }
154
155 let (crate_specifier, path_start_index) = if let Some(first_scope) = path.find("::") {
156 (&path[..first_scope], Some(first_scope + 2))
157 } else {
158 (path, None)
159 };
160
161 let (crate_name, version_req) = if let Some(at) = crate_specifier.find("@") {
162 (
163 &crate_specifier[..at],
164 VersionReq::parse(&crate_specifier[at + 1..]).unwrap_or(VersionReq::STAR),
165 )
166 } else {
167 (crate_specifier, VersionReq::STAR)
168 };
169
170 let Some(crate_data) = self.navigator.load_crate(crate_name, &version_req) else {
171 suggestions.extend(self.navigator.list_available_crates().map(|crate_info| {
172 Suggestion {
173 path: crate_info.name().to_string(),
174 item: None,
175 score: case_aware_jaro_winkler(crate_info.name(), crate_name),
176 }
177 }));
178 return None;
179 };
180
181 let item = crate_data.get(self.navigator, &crate_data.root)?;
182 let Some(path_start_index) = path_start_index else {
183 return Some(item);
184 };
185
186 if let Some(item) = self.find_children_recursive(item, path, path_start_index, suggestions)
187 {
188 return Some(item);
189 }
190
191 let suffix = &path[path_start_index..];
192
193 if let Some(item) = crate_data
196 .path_to_id
197 .get(suffix)
198 .and_then(|id| crate_data.index.get(id))
199 .map(|item| DocRef::new(self.navigator, crate_data, item))
200 {
201 return Some(item);
202 }
203
204 if let Some(sep) = suffix.rfind("::") {
208 let parent_suffix = &suffix[..sep];
209 let child_start = path_start_index + sep + 2;
210 if let Some(parent_id) = crate_data.path_to_id.get(parent_suffix)
211 && let Some(parent_item) = crate_data.index.get(parent_id)
212 {
213 let parent_ref = DocRef::new(self.navigator, crate_data, parent_item);
214 return self.find_children_recursive(parent_ref, path, child_start, suggestions);
215 }
216 }
217
218 None
219 }
220
221 fn find_children_recursive(
224 &mut self,
225 item: DocRef<'a, Item>,
226 path: &str,
227 index: usize,
228 suggestions: &mut Vec<Suggestion<'a>>,
229 ) -> Option<DocRef<'a, Item>> {
230 let remaining = &path[path.len().min(index)..];
231 if remaining.is_empty() {
232 return Some(item);
233 }
234 let segment_end = remaining
235 .find("::")
236 .map(|x| index + x)
237 .unwrap_or(path.len());
238 let segment = &path[index..segment_end];
239 let next_segment_start = path.len().min(segment_end + 2);
240
241 let (kind_filter, segment_name) = parse_discriminated_segment(segment);
242
243 log::trace!(
244 "🔎 searching for {segment_name} (kind={kind_filter:?}) in {} ({:?}) (remaining {})",
245 &path[..index],
246 item.kind(),
247 &path[next_segment_start..]
248 );
249
250 if let Some(found) = self.find_named(item, segment_name, |resolver, candidate| {
251 if !kind_filter.is_none_or(|k| candidate.kind() == k) {
252 return None;
253 }
254 resolver.find_children_recursive(candidate, path, next_segment_start, suggestions)
255 }) {
256 return Some(found);
257 }
258
259 suggestions.extend(self.generate_suggestions(item, path, index));
260 None
261 }
262
263 fn generate_suggestions(
264 &mut self,
265 item: DocRef<'a, Item>,
266 path: &str,
267 index: usize,
268 ) -> Vec<Suggestion<'a>> {
269 let prefix = path[..index].to_owned();
272 let path_owned = path.to_owned();
273 let mut candidates = self.children(item);
274 if let ItemEnum::Trait(trait_) = item.inner() {
278 candidates.extend(trait_.items.iter().filter_map(|id| item.get(id)));
279 }
280 candidates
281 .into_iter()
282 .filter_map(move |item| {
283 item.name().and_then(|name| {
284 let full_path = format!("{prefix}{name}");
285 if path_owned.starts_with(&full_path) {
286 None
287 } else {
288 let score = case_aware_jaro_winkler(&path_owned, &full_path);
289 Some(Suggestion {
290 path: full_path,
291 score,
292 item: Some(item),
293 })
294 }
295 })
296 })
297 .collect()
298 }
299}
300
301impl<'a> Resolver<'a> {
304 pub fn find_child(
307 &mut self,
308 parent: DocRef<'a, Item>,
309 target: &str,
310 ) -> Option<DocRef<'a, Item>> {
311 self.find_named(parent, target, |_, candidate| Some(candidate))
312 }
313
314 pub fn find_by_path<'s>(
316 &mut self,
317 parent: DocRef<'a, Item>,
318 segments: impl IntoIterator<Item = &'s str>,
319 ) -> Option<DocRef<'a, Item>> {
320 let mut current = parent;
321 for segment in segments {
322 current = self.find_child(current, segment)?;
323 }
324 Some(current)
325 }
326
327 fn find_named<F>(
338 &mut self,
339 parent: DocRef<'a, Item>,
340 target: &str,
341 mut accept: F,
342 ) -> Option<DocRef<'a, Item>>
343 where
344 F: FnMut(&mut Self, DocRef<'a, Item>) -> Option<DocRef<'a, Item>>,
345 {
346 self.find_named_dyn(parent, target, &mut accept)
350 }
351
352 #[allow(clippy::type_complexity, reason = "it's fine")]
353 fn find_named_dyn(
354 &mut self,
355 parent: DocRef<'a, Item>,
356 target: &str,
357 accept: &mut dyn FnMut(&mut Self, DocRef<'a, Item>) -> Option<DocRef<'a, Item>>,
358 ) -> Option<DocRef<'a, Item>> {
359 self.with_pushed(parent.into(), Some(target), |resolver| {
360 if matches!(
363 parent.inner(),
364 ItemEnum::Struct(_) | ItemEnum::Enum(_) | ItemEnum::Union(_) | ItemEnum::Trait(_)
365 ) {
366 for method in parent.methods() {
367 if method.name() == Some(target)
368 && let Some(found) = accept(resolver, method)
369 {
370 return Some(found);
371 }
372 }
373 }
374
375 if let ItemEnum::Trait(trait_) = parent.inner() {
380 for assoc in trait_.items.iter().filter_map(|id| parent.get(id)) {
381 if assoc.name() == Some(target)
382 && let Some(found) = accept(resolver, assoc.with_parent(parent))
383 {
384 return Some(found);
385 }
386 }
387 }
388
389 enum Phase1<'a> {
398 Direct(DocRef<'a, Item>),
399 NonGlob(DocRef<'a, Use>),
400 }
401 let mut phase1: Vec<Phase1<'a>> = Vec::new();
402 let mut globs = Vec::<DocRef<'a, Use>>::new();
403 for child in LazyChildren::new(parent) {
404 match child {
405 LazyChild::Item(item) => {
406 if item.name() == Some(target) {
407 phase1.push(Phase1::Direct(item));
408 }
409 }
410 LazyChild::NonGlob { use_item, .. } => {
411 if use_item.item().name == target {
412 phase1.push(Phase1::NonGlob(use_item));
413 }
414 }
415 LazyChild::Glob { use_item, .. } => {
416 globs.push(use_item);
417 }
418 }
419 }
420
421 for cand in phase1 {
422 let resolved = match cand {
423 Phase1::Direct(item) => item,
424 Phase1::NonGlob(use_item) => {
425 let imported_name: &'a str = &use_item.item().name;
426 let Some(target_item) = resolver.follow_use(use_item, parent) else {
427 continue;
428 };
429 target_item.with_name(imported_name)
430 }
431 };
432 if let Some(found) = accept(resolver, resolved) {
433 return Some(found);
434 }
435 }
436
437 for glob_use in globs {
441 if let Some(source_module) = resolver.follow_use(glob_use, parent)
442 && let Some(found) = resolver.find_named_dyn(source_module, target, accept)
443 {
444 return Some(found);
445 }
446 }
447
448 None
449 })
450 .flatten()
451 }
452}
453
454impl<'a> Resolver<'a> {
457 pub fn follow_use(
464 &mut self,
465 use_item: DocRef<'a, Use>,
466 parent_module: DocRef<'a, Item>,
467 ) -> Option<DocRef<'a, Item>> {
468 let use_inner = use_item.item();
469 if let Some(id) = use_inner.id
470 && let Some(target) = use_item.crate_docs().get(self.navigator, &id)
471 {
472 return Some(target);
473 }
474
475 self.with_pushed(use_item.into(), None, |resolver| {
476 let module_path = parent_module.containing_module_path();
477 let rewritten = rewrite_relative_prefix(&module_path, &use_inner.source)?;
478 resolver.resolve_path(&rewritten, &mut vec![])
479 })
480 .flatten()
481 }
482
483 pub fn resolve_lazy_child(&mut self, child: LazyChild<'a>) -> Option<DocRef<'a, Item>> {
486 match child {
487 LazyChild::Item(item) => Some(item),
488 LazyChild::NonGlob { use_item, parent } => {
489 let name: &'a str = &use_item.item().name;
490 self.follow_use(use_item, parent).map(|i| i.with_name(name))
491 }
492 LazyChild::Glob { use_item, parent } => self.follow_use(use_item, parent),
493 }
494 }
495}
496
497impl<'a> Resolver<'a> {
500 pub fn children(&mut self, item: DocRef<'a, Item>) -> Vec<DocRef<'a, Item>> {
503 let mut out = Vec::new();
504 self.collect_children(item, false, ParentRef::from(item), &mut out);
505 out
506 }
507
508 pub fn children_including_uses(&mut self, item: DocRef<'a, Item>) -> Vec<DocRef<'a, Item>> {
511 let mut out = Vec::new();
512 self.collect_children(item, true, ParentRef::from(item), &mut out);
513 out
514 }
515
516 pub fn ids(&mut self, parent: DocRef<'a, Item>, ids: &'a [Id]) -> Vec<DocRef<'a, Item>> {
519 let mut out = Vec::new();
520 self.collect_ids(parent, ids, false, Some(ParentRef::from(parent)), &mut out);
521 out
522 }
523
524 pub fn ids_including_uses(
527 &mut self,
528 parent: DocRef<'a, Item>,
529 ids: &'a [Id],
530 ) -> Vec<DocRef<'a, Item>> {
531 let mut out = Vec::new();
532 self.collect_ids(parent, ids, true, Some(ParentRef::from(parent)), &mut out);
533 out
534 }
535
536 fn collect_children(
537 &mut self,
538 item: DocRef<'a, Item>,
539 include_uses: bool,
540 parent_ref: ParentRef<'a>,
541 out: &mut Vec<DocRef<'a, Item>>,
542 ) {
543 match item.inner() {
544 ItemEnum::Module(module) => {
545 self.collect_ids(item, &module.items, include_uses, Some(parent_ref), out);
546 }
547 ItemEnum::Enum(enum_item) => {
548 self.collect_ids(
549 item,
550 &enum_item.variants,
551 include_uses,
552 Some(parent_ref),
553 out,
554 );
555 out.extend(item.methods());
556 }
557 ItemEnum::Struct(_) | ItemEnum::Union(_) => {
558 out.extend(item.methods());
559 }
560 ItemEnum::Use(use_item) => {
561 let use_ref = item.build_ref(use_item);
562 self.collect_use_children(use_ref, item, include_uses, out);
563 }
564 _ => {}
565 }
566 }
567
568 fn collect_ids(
569 &mut self,
570 parent: DocRef<'a, Item>,
571 ids: &'a [Id],
572 include_uses: bool,
573 parent_ref: Option<ParentRef<'a>>,
574 out: &mut Vec<DocRef<'a, Item>>,
575 ) {
576 for id in ids {
577 let Some(item) = parent.get(id) else { continue };
578 if let ItemEnum::Use(use_item) = item.inner() {
579 if include_uses {
580 out.push(item);
581 continue;
582 }
583 let use_ref = item.build_ref(use_item);
584 self.collect_use_children(use_ref, parent, include_uses, out);
585 } else {
586 let item = match parent_ref {
587 Some(p) => item.with_parent(p),
588 None => item,
589 };
590 out.push(item);
591 }
592 }
593 }
594
595 fn collect_use_children(
596 &mut self,
597 use_ref: DocRef<'a, Use>,
598 parent: DocRef<'a, Item>,
599 include_uses: bool,
600 out: &mut Vec<DocRef<'a, Item>>,
601 ) {
602 let Some(source_item) = self.follow_use(use_ref, parent) else {
603 return;
604 };
605 let use_item = use_ref.item();
606 if use_item.is_glob {
607 match source_item.inner() {
608 ItemEnum::Module(module) => {
609 self.collect_ids(source_item, &module.items, include_uses, None, out);
610 }
611 ItemEnum::Enum(enum_item) => {
612 self.collect_ids(source_item, &enum_item.variants, include_uses, None, out);
613 }
614 _ => {}
615 }
616 } else {
617 out.push(source_item.with_name(&use_item.name));
622 }
623 }
624}
625
626impl<'a> Resolver<'a> {
629 pub fn get_path(&mut self, origin: DocRef<'a, Item>, id: Id) -> Option<DocRef<'a, Item>> {
634 let item_summary = origin.crate_docs().paths.get(&id)?;
635 let crate_ = origin
636 .crate_docs()
637 .traverse_to_crate_by_id(self.navigator, item_summary.crate_id)?;
638 let root = crate_.root_item(self.navigator);
639 self.find_by_path(root, item_summary.path.iter().skip(1).map(String::as_str))
640 }
641
642 pub fn get_item_from_id_path(
645 &mut self,
646 crate_name: &str,
647 ids: &[u32],
648 ) -> Option<(DocRef<'a, Item>, Vec<&'a str>)> {
649 let mut path = vec![];
650 let crate_docs = self.navigator.load_crate(crate_name, &VersionReq::STAR)?;
651 let mut item = crate_docs.get(self.navigator, &crate_docs.root)?;
652 path.push(item.crate_docs().name());
653 for id in ids {
654 item = item.get(&Id(*id))?;
655 if let ItemEnum::Use(use_item) = item.inner() {
656 let use_ref = item.build_ref(use_item);
657 let parent = item;
658 item = self
659 .follow_use(use_ref, parent)
660 .or_else(|| self.resolve_path(&use_item.source, &mut vec![]))?;
661 if !use_item.is_glob {
662 item.set_name(&use_item.name);
663 }
664 } else if let Some(name) = item.name() {
665 path.push(name);
666 }
667 }
668 Some((item, path))
669 }
670}
671
672fn parse_discriminated_segment(segment: &str) -> (Option<ItemKind>, &str) {
677 let Some(at) = segment.find('@') else {
678 return (None, segment);
679 };
680 let (disc, name) = (&segment[..at], &segment[at + 1..]);
681 match disc {
682 "mod" | "module" => (Some(ItemKind::Module), name),
683 "struct" => (Some(ItemKind::Struct), name),
684 "enum" => (Some(ItemKind::Enum), name),
685 "union" => (Some(ItemKind::Union), name),
686 "trait" => (Some(ItemKind::Trait), name),
687 "traitalias" => (Some(ItemKind::TraitAlias), name),
688 "fn" | "function" | "method" => (Some(ItemKind::Function), name),
689 "tyalias" | "typealias" => (Some(ItemKind::TypeAlias), name),
690 "type" => (Some(ItemKind::AssocType), name),
691 "const" | "constant" => (Some(ItemKind::Constant), name),
692 "static" => (Some(ItemKind::Static), name),
693 "macro" => (Some(ItemKind::Macro), name),
694 "attr" => (Some(ItemKind::ProcAttribute), name),
695 "derive" => (Some(ItemKind::ProcDerive), name),
696 "prim" | "primitive" => (Some(ItemKind::Primitive), name),
697 "field" => (Some(ItemKind::StructField), name),
698 "variant" => (Some(ItemKind::Variant), name),
699 "value" => (None, name),
700 _ => (None, segment),
701 }
702}
703
704fn rewrite_relative_prefix(module_path: &[&str], source: &str) -> Option<String> {
709 let mut supers = 0;
710 let mut rest = source;
711 while let Some(tail) = rest.strip_prefix("super::") {
712 supers += 1;
713 rest = tail;
714 }
715 if supers > 0 || rest == "super" {
716 let supers = if rest == "super" { supers + 1 } else { supers };
717 let tail = if rest == "super" { "" } else { rest };
718 let remaining = module_path.len().checked_sub(supers)?;
719 if remaining == 0 {
720 return None;
721 }
722 let prefix = module_path[..remaining].join("::");
723 return Some(if tail.is_empty() {
724 prefix
725 } else {
726 format!("{prefix}::{tail}")
727 });
728 }
729
730 if let Some(tail) = source.strip_prefix("self::") {
731 return Some(format!("{}::{}", module_path.join("::"), tail));
732 }
733 if source == "self" {
734 return Some(module_path.join("::"));
735 }
736
737 if let Some(tail) = source.strip_prefix("crate::") {
738 let crate_name = module_path.first().copied()?;
739 return Some(format!("{crate_name}::{tail}"));
740 }
741 if source == "crate" {
742 return module_path.first().copied().map(String::from);
743 }
744
745 Some(source.to_owned())
746}