1use std::mem;
8use std::{cell::LazyCell, cmp::Reverse};
9
10use base_db::{RootQueryDb, SourceDatabase};
11use either::Either;
12use hir::{
13 Adt, AsAssocItem, DefWithBody, EditionedFileId, FileRange, FileRangeWrapper, HasAttrs,
14 HasContainer, HasSource, InFile, InFileWrapper, InRealFile, InlineAsmOperand, ItemContainer,
15 ModuleSource, PathResolution, Semantics, Visibility, sym,
16};
17use memchr::memmem::Finder;
18use parser::SyntaxKind;
19use rustc_hash::{FxHashMap, FxHashSet};
20use salsa::Database;
21use syntax::{
22 AstNode, AstToken, SmolStr, SyntaxElement, SyntaxNode, TextRange, TextSize, ToSmolStr,
23 ast::{self, HasName, Rename},
24 match_ast,
25};
26use triomphe::Arc;
27
28use crate::{
29 RootDatabase,
30 defs::{Definition, NameClass, NameRefClass},
31 traits::{as_trait_assoc_def, convert_to_def_in_trait},
32};
33
34#[derive(Debug, Default, Clone)]
35pub struct UsageSearchResult {
36 pub references: FxHashMap<EditionedFileId, Vec<FileReference>>,
37}
38
39impl UsageSearchResult {
40 pub fn is_empty(&self) -> bool {
41 self.references.is_empty()
42 }
43
44 pub fn len(&self) -> usize {
45 self.references.len()
46 }
47
48 pub fn iter(&self) -> impl Iterator<Item = (EditionedFileId, &[FileReference])> + '_ {
49 self.references.iter().map(|(&file_id, refs)| (file_id, &**refs))
50 }
51
52 pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
53 self.references.iter().flat_map(|(&file_id, refs)| {
54 refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range })
55 })
56 }
57}
58
59impl IntoIterator for UsageSearchResult {
60 type Item = (EditionedFileId, Vec<FileReference>);
61 type IntoIter = <FxHashMap<EditionedFileId, Vec<FileReference>> as IntoIterator>::IntoIter;
62
63 fn into_iter(self) -> Self::IntoIter {
64 self.references.into_iter()
65 }
66}
67
68#[derive(Debug, Clone)]
69pub struct FileReference {
70 pub range: TextRange,
72 pub name: FileReferenceNode,
74 pub category: ReferenceCategory,
75}
76
77#[derive(Debug, Clone)]
78pub enum FileReferenceNode {
79 Name(ast::Name),
80 NameRef(ast::NameRef),
81 Lifetime(ast::Lifetime),
82 FormatStringEntry(ast::String, TextRange),
83}
84
85impl FileReferenceNode {
86 pub fn text_range(&self) -> TextRange {
87 match self {
88 FileReferenceNode::Name(it) => it.syntax().text_range(),
89 FileReferenceNode::NameRef(it) => it.syntax().text_range(),
90 FileReferenceNode::Lifetime(it) => it.syntax().text_range(),
91 FileReferenceNode::FormatStringEntry(_, range) => *range,
92 }
93 }
94 pub fn syntax(&self) -> SyntaxElement {
95 match self {
96 FileReferenceNode::Name(it) => it.syntax().clone().into(),
97 FileReferenceNode::NameRef(it) => it.syntax().clone().into(),
98 FileReferenceNode::Lifetime(it) => it.syntax().clone().into(),
99 FileReferenceNode::FormatStringEntry(it, _) => it.syntax().clone().into(),
100 }
101 }
102 pub fn into_name_like(self) -> Option<ast::NameLike> {
103 match self {
104 FileReferenceNode::Name(it) => Some(ast::NameLike::Name(it)),
105 FileReferenceNode::NameRef(it) => Some(ast::NameLike::NameRef(it)),
106 FileReferenceNode::Lifetime(it) => Some(ast::NameLike::Lifetime(it)),
107 FileReferenceNode::FormatStringEntry(_, _) => None,
108 }
109 }
110 pub fn as_name_ref(&self) -> Option<&ast::NameRef> {
111 match self {
112 FileReferenceNode::NameRef(name_ref) => Some(name_ref),
113 _ => None,
114 }
115 }
116 pub fn as_lifetime(&self) -> Option<&ast::Lifetime> {
117 match self {
118 FileReferenceNode::Lifetime(lifetime) => Some(lifetime),
119 _ => None,
120 }
121 }
122 pub fn text(&self) -> syntax::TokenText<'_> {
123 match self {
124 FileReferenceNode::NameRef(name_ref) => name_ref.text(),
125 FileReferenceNode::Name(name) => name.text(),
126 FileReferenceNode::Lifetime(lifetime) => lifetime.text(),
127 FileReferenceNode::FormatStringEntry(it, range) => {
128 syntax::TokenText::borrowed(&it.text()[*range - it.syntax().text_range().start()])
129 }
130 }
131 }
132}
133
134bitflags::bitflags! {
135 #[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
136 pub struct ReferenceCategory: u8 {
137 const WRITE = 1 << 0;
140 const READ = 1 << 1;
141 const IMPORT = 1 << 2;
142 const TEST = 1 << 3;
143 }
144}
145
146#[derive(Clone, Debug)]
151pub struct SearchScope {
152 entries: FxHashMap<EditionedFileId, Option<TextRange>>,
153}
154
155impl SearchScope {
156 fn new(entries: FxHashMap<EditionedFileId, Option<TextRange>>) -> SearchScope {
157 SearchScope { entries }
158 }
159
160 fn crate_graph(db: &RootDatabase) -> SearchScope {
162 let mut entries = FxHashMap::default();
163
164 let all_crates = db.all_crates();
165 for &krate in all_crates.iter() {
166 let crate_data = krate.data(db);
167 let source_root = db.file_source_root(crate_data.root_file_id).source_root_id(db);
168 let source_root = db.source_root(source_root).source_root(db);
169 entries.extend(
170 source_root
171 .iter()
172 .map(|id| (EditionedFileId::new(db, id, crate_data.edition), None)),
173 );
174 }
175 SearchScope { entries }
176 }
177
178 fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope {
180 let mut entries = FxHashMap::default();
181 for rev_dep in of.transitive_reverse_dependencies(db) {
182 let root_file = rev_dep.root_file(db);
183
184 let source_root = db.file_source_root(root_file).source_root_id(db);
185 let source_root = db.source_root(source_root).source_root(db);
186 entries.extend(
187 source_root
188 .iter()
189 .map(|id| (EditionedFileId::new(db, id, rev_dep.edition(db)), None)),
190 );
191 }
192 SearchScope { entries }
193 }
194
195 fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope {
197 let root_file = of.root_file(db);
198
199 let source_root_id = db.file_source_root(root_file).source_root_id(db);
200 let source_root = db.source_root(source_root_id).source_root(db);
201 SearchScope {
202 entries: source_root
203 .iter()
204 .map(|id| (EditionedFileId::new(db, id, of.edition(db)), None))
205 .collect(),
206 }
207 }
208
209 pub fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
211 let mut entries = FxHashMap::default();
212
213 let (file_id, range) = {
214 let InFile { file_id, value } = module.definition_source_range(db);
215 if let Some(InRealFile { file_id, value: call_source }) = file_id.original_call_node(db)
216 {
217 (file_id, Some(call_source.text_range()))
218 } else {
219 (file_id.original_file(db), Some(value))
220 }
221 };
222 entries.entry(file_id).or_insert(range);
223
224 let mut to_visit: Vec<_> = module.children(db).collect();
225 while let Some(module) = to_visit.pop() {
226 if let Some(file_id) = module.as_source_file_id(db) {
227 entries.insert(file_id, None);
228 }
229 to_visit.extend(module.children(db));
230 }
231 SearchScope { entries }
232 }
233
234 pub fn empty() -> SearchScope {
236 SearchScope::new(FxHashMap::default())
237 }
238
239 pub fn single_file(file: EditionedFileId) -> SearchScope {
241 SearchScope::new(std::iter::once((file, None)).collect())
242 }
243
244 pub fn file_range(range: FileRange) -> SearchScope {
246 SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
247 }
248
249 pub fn files(files: &[EditionedFileId]) -> SearchScope {
251 SearchScope::new(files.iter().map(|f| (*f, None)).collect())
252 }
253
254 pub fn intersection(&self, other: &SearchScope) -> SearchScope {
255 let (mut small, mut large) = (&self.entries, &other.entries);
256 if small.len() > large.len() {
257 mem::swap(&mut small, &mut large)
258 }
259
260 let intersect_ranges =
261 |r1: Option<TextRange>, r2: Option<TextRange>| -> Option<Option<TextRange>> {
262 match (r1, r2) {
263 (None, r) | (r, None) => Some(r),
264 (Some(r1), Some(r2)) => r1.intersect(r2).map(Some),
265 }
266 };
267 let res = small
268 .iter()
269 .filter_map(|(&file_id, &r1)| {
270 let &r2 = large.get(&file_id)?;
271 let r = intersect_ranges(r1, r2)?;
272 Some((file_id, r))
273 })
274 .collect();
275
276 SearchScope::new(res)
277 }
278}
279
280impl IntoIterator for SearchScope {
281 type Item = (EditionedFileId, Option<TextRange>);
282 type IntoIter = std::collections::hash_map::IntoIter<EditionedFileId, Option<TextRange>>;
283
284 fn into_iter(self) -> Self::IntoIter {
285 self.entries.into_iter()
286 }
287}
288
289impl Definition {
290 fn search_scope(&self, db: &RootDatabase) -> SearchScope {
291 let _p = tracing::info_span!("search_scope").entered();
292
293 if let Definition::BuiltinType(_) = self {
294 return SearchScope::crate_graph(db);
295 }
296
297 if let &Definition::Module(module) = self
299 && module.is_crate_root()
300 {
301 return SearchScope::reverse_dependencies(db, module.krate());
302 }
303
304 let module = match self.module(db) {
305 Some(it) => it,
306 None => return SearchScope::empty(),
307 };
308 let InFile { file_id, value: module_source } = module.definition_source(db);
309 let file_id = file_id.original_file(db);
310
311 if let Definition::Local(var) = self {
312 let def = match var.parent(db) {
313 DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
314 DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
315 DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
316 DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
317 };
318 return match def {
319 Some(def) => SearchScope::file_range(
320 def.as_ref().original_file_range_with_macro_call_input(db),
321 ),
322 None => SearchScope::single_file(file_id),
323 };
324 }
325
326 if let Definition::InlineAsmOperand(op) = self {
327 let def = match op.parent(db) {
328 DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
329 DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
330 DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
331 DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
332 };
333 return match def {
334 Some(def) => SearchScope::file_range(
335 def.as_ref().original_file_range_with_macro_call_input(db),
336 ),
337 None => SearchScope::single_file(file_id),
338 };
339 }
340
341 if let Definition::SelfType(impl_) = self {
342 return match impl_.source(db).map(|src| src.syntax().cloned()) {
343 Some(def) => SearchScope::file_range(
344 def.as_ref().original_file_range_with_macro_call_input(db),
345 ),
346 None => SearchScope::single_file(file_id),
347 };
348 }
349
350 if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
351 let def = match param.parent(db) {
352 hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
353 hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
354 hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
355 hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
356 hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
357 hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
358 hir::GenericDef::Static(it) => it.source(db).map(|src| src.syntax().cloned()),
359 };
360 return match def {
361 Some(def) => SearchScope::file_range(
362 def.as_ref().original_file_range_with_macro_call_input(db),
363 ),
364 None => SearchScope::single_file(file_id),
365 };
366 }
367
368 if let Definition::Macro(macro_def) = self {
369 return match macro_def.kind(db) {
370 hir::MacroKind::Declarative => {
371 if macro_def.attrs(db).by_key(sym::macro_export).exists() {
372 SearchScope::reverse_dependencies(db, module.krate())
373 } else {
374 SearchScope::krate(db, module.krate())
375 }
376 }
377 hir::MacroKind::AttrBuiltIn
378 | hir::MacroKind::DeriveBuiltIn
379 | hir::MacroKind::DeclarativeBuiltIn => SearchScope::crate_graph(db),
380 hir::MacroKind::Derive | hir::MacroKind::Attr | hir::MacroKind::ProcMacro => {
381 SearchScope::reverse_dependencies(db, module.krate())
382 }
383 };
384 }
385
386 if let Definition::DeriveHelper(_) = self {
387 return SearchScope::reverse_dependencies(db, module.krate());
388 }
389
390 let vis = self.visibility(db);
391 if let Some(Visibility::Public) = vis {
392 return SearchScope::reverse_dependencies(db, module.krate());
393 }
394 if let Some(Visibility::Module(module, _)) = vis {
395 return SearchScope::module_and_children(db, module.into());
396 }
397
398 let range = match module_source {
399 ModuleSource::Module(m) => Some(m.syntax().text_range()),
400 ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()),
401 ModuleSource::SourceFile(_) => None,
402 };
403 match range {
404 Some(range) => SearchScope::file_range(FileRange { file_id, range }),
405 None => SearchScope::single_file(file_id),
406 }
407 }
408
409 pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> {
410 FindUsages {
411 def: self,
412 rename: None,
413 assoc_item_container: self.as_assoc_item(sema.db).map(|a| a.container(sema.db)),
414 sema,
415 scope: None,
416 include_self_kw_refs: None,
417 search_self_mod: false,
418 }
419 }
420}
421
422#[derive(Clone)]
423pub struct FindUsages<'a> {
424 def: Definition,
425 rename: Option<&'a Rename>,
426 sema: &'a Semantics<'a, RootDatabase>,
427 scope: Option<&'a SearchScope>,
428 assoc_item_container: Option<hir::AssocItemContainer>,
430 include_self_kw_refs: Option<hir::Type<'a>>,
432 search_self_mod: bool,
434}
435
436impl<'a> FindUsages<'a> {
437 pub fn include_self_refs(mut self) -> Self {
439 self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
440 self.search_self_mod = true;
441 self
442 }
443
444 pub fn in_scope(self, scope: &'a SearchScope) -> Self {
446 self.set_scope(Some(scope))
447 }
448
449 pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self {
451 assert!(self.scope.is_none());
452 self.scope = scope;
453 self
454 }
455
456 pub fn with_rename(mut self, rename: Option<&'a Rename>) -> Self {
460 self.rename = rename;
461 self
462 }
463
464 pub fn at_least_one(&self) -> bool {
465 let mut found = false;
466 self.search(&mut |_, _| {
467 found = true;
468 true
469 });
470 found
471 }
472
473 pub fn all(self) -> UsageSearchResult {
474 let mut res = UsageSearchResult::default();
475 self.search(&mut |file_id, reference| {
476 res.references.entry(file_id).or_default().push(reference);
477 false
478 });
479 res
480 }
481
482 fn scope_files<'b>(
483 db: &'b RootDatabase,
484 scope: &'b SearchScope,
485 ) -> impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)> + 'b {
486 scope.entries.iter().map(|(&file_id, &search_range)| {
487 let text = db.file_text(file_id.file_id(db)).text(db);
488 let search_range =
489 search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
490
491 (text.clone(), file_id, search_range)
492 })
493 }
494
495 fn match_indices<'b>(
496 text: &'b str,
497 finder: &'b Finder<'b>,
498 search_range: TextRange,
499 ) -> impl Iterator<Item = TextSize> + 'b {
500 finder.find_iter(text.as_bytes()).filter_map(move |idx| {
501 let offset: TextSize = idx.try_into().unwrap();
502 if !search_range.contains_inclusive(offset) {
503 return None;
504 }
505 if text[..idx]
509 .chars()
510 .next_back()
511 .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_'))
512 || text[idx + finder.needle().len()..]
513 .chars()
514 .next()
515 .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_' | '0'..='9'))
516 {
517 return None;
518 }
519 Some(offset)
520 })
521 }
522
523 fn find_nodes<'b>(
524 sema: &'b Semantics<'_, RootDatabase>,
525 name: &str,
526 file_id: EditionedFileId,
527 node: &syntax::SyntaxNode,
528 offset: TextSize,
529 ) -> impl Iterator<Item = SyntaxNode> + 'b {
530 node.token_at_offset(offset)
531 .find(|it| {
532 it.text().trim_start_matches('\'').trim_start_matches("r#") == name
534 })
535 .into_iter()
536 .flat_map(move |token| {
537 if sema.is_inside_macro_call(InFile::new(file_id.into(), &token)) {
538 sema.descend_into_macros_exact(token)
539 } else {
540 <_>::from([token])
541 }
542 .into_iter()
543 .filter_map(|it| it.parent())
544 })
545 }
546
547 fn short_associated_function_fast_search(
563 &self,
564 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
565 search_scope: &SearchScope,
566 name: &str,
567 ) -> bool {
568 if self.scope.is_some() {
569 return false;
570 }
571
572 let _p = tracing::info_span!("short_associated_function_fast_search").entered();
573
574 let container = (|| {
575 let Definition::Function(function) = self.def else {
576 return None;
577 };
578 if function.has_self_param(self.sema.db) {
579 return None;
580 }
581 match function.container(self.sema.db) {
582 ItemContainer::Impl(impl_) => {
585 let has_trait = impl_.trait_(self.sema.db).is_some();
586 if has_trait {
587 return None;
588 }
589 let adt = impl_.self_ty(self.sema.db).as_adt()?;
590 Some(adt)
591 }
592 _ => None,
593 }
594 })();
595 let Some(container) = container else {
596 return false;
597 };
598
599 fn has_any_name(node: &SyntaxNode, mut predicate: impl FnMut(&str) -> bool) -> bool {
600 node.descendants().any(|node| {
601 match_ast! {
602 match node {
603 ast::Name(it) => predicate(it.text().trim_start_matches("r#")),
604 ast::NameRef(it) => predicate(it.text().trim_start_matches("r#")),
605 _ => false
606 }
607 }
608 })
609 }
610
611 fn collect_possible_aliases(
616 sema: &Semantics<'_, RootDatabase>,
617 container: Adt,
618 ) -> Option<(FxHashSet<SmolStr>, Vec<FileRangeWrapper<EditionedFileId>>)> {
619 fn insert_type_alias(
620 db: &RootDatabase,
621 to_process: &mut Vec<(SmolStr, SearchScope)>,
622 alias_name: &str,
623 def: Definition,
624 ) {
625 let alias = alias_name.trim_start_matches("r#").to_smolstr();
626 tracing::debug!("found alias: {alias}");
627 to_process.push((alias, def.search_scope(db)));
628 }
629
630 let _p = tracing::info_span!("collect_possible_aliases").entered();
631
632 let db = sema.db;
633 let container_name = container.name(db).as_str().to_smolstr();
634 let search_scope = Definition::from(container).search_scope(db);
635 let mut seen = FxHashSet::default();
636 let mut completed = FxHashSet::default();
637 let mut to_process = vec![(container_name, search_scope)];
638 let mut is_possibly_self = Vec::new();
639 let mut total_files_searched = 0;
640
641 while let Some((current_to_process, current_to_process_search_scope)) = to_process.pop()
642 {
643 let is_alias = |alias: &ast::TypeAlias| {
644 let def = sema.to_def(alias)?;
645 let ty = def.ty(db);
646 let is_alias = ty.as_adt()? == container;
647 is_alias.then_some(def)
648 };
649
650 let finder = Finder::new(current_to_process.as_bytes());
651 for (file_text, file_id, search_range) in
652 FindUsages::scope_files(db, ¤t_to_process_search_scope)
653 {
654 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
655
656 for offset in FindUsages::match_indices(&file_text, &finder, search_range) {
657 let usages = FindUsages::find_nodes(
658 sema,
659 ¤t_to_process,
660 file_id,
661 &tree,
662 offset,
663 )
664 .filter(|it| matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF));
665 for usage in usages {
666 if let Some(alias) = usage.parent().and_then(|it| {
667 let path = ast::PathSegment::cast(it)?.parent_path();
668 let use_tree = ast::UseTree::cast(path.syntax().parent()?)?;
669 use_tree.rename()?.name()
670 }) {
671 if seen.insert(InFileWrapper::new(
672 file_id,
673 alias.syntax().text_range(),
674 )) {
675 tracing::debug!("found alias: {alias}");
676 cov_mark::hit!(container_use_rename);
677 to_process.push((
679 alias.text().to_smolstr(),
680 current_to_process_search_scope.clone(),
681 ));
682 }
683 } else if let Some(alias) =
684 usage.ancestors().find_map(ast::TypeAlias::cast)
685 && let Some(name) = alias.name()
686 && seen
687 .insert(InFileWrapper::new(file_id, name.syntax().text_range()))
688 {
689 if let Some(def) = is_alias(&alias) {
690 cov_mark::hit!(container_type_alias);
691 insert_type_alias(
692 sema.db,
693 &mut to_process,
694 name.text().as_str(),
695 def.into(),
696 );
697 } else {
698 cov_mark::hit!(same_name_different_def_type_alias);
699 }
700 }
701
702 let impl_ = 'impl_: {
704 for ancestor in usage.ancestors() {
705 if let Some(parent) = ancestor.parent()
706 && let Some(parent) = ast::Impl::cast(parent)
707 {
708 if matches!(
710 ancestor.kind(),
711 SyntaxKind::ASSOC_ITEM_LIST
712 | SyntaxKind::WHERE_CLAUSE
713 | SyntaxKind::GENERIC_PARAM_LIST
714 ) {
715 break;
716 }
717 if parent
718 .trait_()
719 .is_some_and(|trait_| *trait_.syntax() == ancestor)
720 {
721 break;
722 }
723
724 break 'impl_ Some(parent);
726 }
727 }
728 None
729 };
730 (|| {
731 let impl_ = impl_?;
732 is_possibly_self.push(sema.original_range(impl_.syntax()));
733 let assoc_items = impl_.assoc_item_list()?;
734 let type_aliases = assoc_items
735 .syntax()
736 .descendants()
737 .filter_map(ast::TypeAlias::cast);
738 for type_alias in type_aliases {
739 let Some(ty) = type_alias.ty() else { continue };
740 let Some(name) = type_alias.name() else { continue };
741 let contains_self = ty
742 .syntax()
743 .descendants_with_tokens()
744 .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
745 if !contains_self {
746 continue;
747 }
748 if seen.insert(InFileWrapper::new(
749 file_id,
750 name.syntax().text_range(),
751 )) {
752 if let Some(def) = is_alias(&type_alias) {
753 cov_mark::hit!(self_type_alias);
754 insert_type_alias(
755 sema.db,
756 &mut to_process,
757 name.text().as_str(),
758 def.into(),
759 );
760 } else {
761 cov_mark::hit!(same_name_different_def_type_alias);
762 }
763 }
764 }
765 Some(())
766 })();
767 }
768 }
769 }
770
771 completed.insert(current_to_process);
772
773 total_files_searched += current_to_process_search_scope.entries.len();
774 if total_files_searched > 20_000 && completed.len() > 100 {
776 tracing::info!(aliases_count = %completed.len(), "too much aliases; leaving fast path");
781 return None;
782 }
783 }
784
785 is_possibly_self.sort_unstable_by_key(|position| {
787 (position.file_id, position.range.start(), Reverse(position.range.end()))
788 });
789 is_possibly_self.dedup_by(|pos2, pos1| {
790 pos1.file_id == pos2.file_id
791 && pos1.range.start() <= pos2.range.start()
792 && pos1.range.end() >= pos2.range.end()
793 });
794
795 tracing::info!(aliases_count = %completed.len(), "aliases search completed");
796
797 Some((completed, is_possibly_self))
798 }
799
800 fn search(
801 this: &FindUsages<'_>,
802 finder: &Finder<'_>,
803 name: &str,
804 files: impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)>,
805 mut container_predicate: impl FnMut(
806 &SyntaxNode,
807 InFileWrapper<EditionedFileId, TextRange>,
808 ) -> bool,
809 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
810 ) {
811 for (file_text, file_id, search_range) in files {
812 let tree = LazyCell::new(move || this.sema.parse(file_id).syntax().clone());
813
814 for offset in FindUsages::match_indices(&file_text, finder, search_range) {
815 let usages = FindUsages::find_nodes(this.sema, name, file_id, &tree, offset)
816 .filter_map(ast::NameRef::cast);
817 for usage in usages {
818 let found_usage = usage
819 .syntax()
820 .parent()
821 .and_then(ast::PathSegment::cast)
822 .map(|path_segment| {
823 container_predicate(
824 path_segment.parent_path().syntax(),
825 InFileWrapper::new(file_id, usage.syntax().text_range()),
826 )
827 })
828 .unwrap_or(false);
829 if found_usage {
830 this.found_name_ref(&usage, sink);
831 }
832 }
833 }
834 }
835 }
836
837 let Some((container_possible_aliases, is_possibly_self)) =
838 collect_possible_aliases(self.sema, container)
839 else {
840 return false;
841 };
842
843 cov_mark::hit!(short_associated_function_fast_search);
844
845 let finder = Finder::new(name.as_bytes());
848 let mut self_positions = FxHashSet::default();
850 tracing::info_span!("Self_search").in_scope(|| {
851 search(
852 self,
853 &finder,
854 name,
855 is_possibly_self.into_iter().map(|position| {
856 (position.file_text(self.sema.db).clone(), position.file_id, position.range)
857 }),
858 |path, name_position| {
859 let has_self = path
860 .descendants_with_tokens()
861 .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
862 if has_self {
863 self_positions.insert(name_position);
864 }
865 has_self
866 },
867 sink,
868 )
869 });
870 tracing::info_span!("aliases_search").in_scope(|| {
871 search(
872 self,
873 &finder,
874 name,
875 FindUsages::scope_files(self.sema.db, search_scope),
876 |path, name_position| {
877 has_any_name(path, |name| container_possible_aliases.contains(name))
878 && !self_positions.contains(&name_position)
879 },
880 sink,
881 )
882 });
883
884 true
885 }
886
887 pub fn search(&self, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool) {
888 let _p = tracing::info_span!("FindUsages:search").entered();
889 let sema = self.sema;
890
891 let search_scope = {
892 let base =
894 as_trait_assoc_def(sema.db, self.def).unwrap_or(self.def).search_scope(sema.db);
895 match &self.scope {
896 None => base,
897 Some(scope) => base.intersection(scope),
898 }
899 };
900
901 let name = match (self.rename, self.def) {
902 (Some(rename), _) => {
903 if rename.underscore_token().is_some() {
904 None
905 } else {
906 rename.name().map(|n| n.to_smolstr())
907 }
908 }
909 (_, Definition::Module(module)) if module.is_crate_root() => {
911 module
916 .krate()
917 .display_name(self.sema.db)
918 .map(|crate_name| crate_name.crate_name().symbol().as_str().into())
919 }
920 _ => {
921 let self_kw_refs = || {
922 self.include_self_kw_refs.as_ref().and_then(|ty| {
923 ty.as_adt()
924 .map(|adt| adt.name(self.sema.db))
925 .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
926 })
927 };
928 self.def
932 .name(sema.db)
933 .or_else(self_kw_refs)
934 .map(|it| it.as_str().trim_start_matches('\'').to_smolstr())
935 }
936 };
937 let name = match &name {
938 Some(s) => s.as_str(),
939 None => return,
940 };
941
942 if name.len() <= 7 && self.short_associated_function_fast_search(sink, &search_scope, name)
944 {
945 return;
946 }
947
948 let finder = &Finder::new(name);
949 let include_self_kw_refs =
950 self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
951 for (text, file_id, search_range) in Self::scope_files(sema.db, &search_scope) {
952 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
953
954 for offset in Self::match_indices(&text, finder, search_range) {
956 let ret = tree.token_at_offset(offset).any(|token| {
957 if let Some((range, _frange, string_token, Some(nameres))) =
958 sema.check_for_format_args_template(token.clone(), offset)
959 {
960 return self.found_format_args_ref(
961 file_id,
962 range,
963 string_token,
964 nameres,
965 sink,
966 );
967 }
968 false
969 });
970 if ret {
971 return;
972 }
973
974 for name in Self::find_nodes(sema, name, file_id, &tree, offset)
975 .filter_map(ast::NameLike::cast)
976 {
977 if match name {
978 ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
979 ast::NameLike::Name(name) => self.found_name(&name, sink),
980 ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
981 } {
982 return;
983 }
984 }
985 }
986 if let Some((self_ty, finder)) = &include_self_kw_refs {
988 for offset in Self::match_indices(&text, finder, search_range) {
989 for name_ref in Self::find_nodes(sema, "Self", file_id, &tree, offset)
990 .filter_map(ast::NameRef::cast)
991 {
992 if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
993 return;
994 }
995 }
996 }
997 }
998 }
999
1000 if let Definition::Module(module) = self.def {
1002 let scope =
1003 search_scope.intersection(&SearchScope::module_and_children(self.sema.db, module));
1004
1005 let is_crate_root = module.is_crate_root().then(|| Finder::new("crate"));
1006 let finder = &Finder::new("super");
1007
1008 for (text, file_id, search_range) in Self::scope_files(sema.db, &scope) {
1009 self.sema.db.unwind_if_revision_cancelled();
1010
1011 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
1012
1013 for offset in Self::match_indices(&text, finder, search_range) {
1014 for name_ref in Self::find_nodes(sema, "super", file_id, &tree, offset)
1015 .filter_map(ast::NameRef::cast)
1016 {
1017 if self.found_name_ref(&name_ref, sink) {
1018 return;
1019 }
1020 }
1021 }
1022 if let Some(finder) = &is_crate_root {
1023 for offset in Self::match_indices(&text, finder, search_range) {
1024 for name_ref in Self::find_nodes(sema, "crate", file_id, &tree, offset)
1025 .filter_map(ast::NameRef::cast)
1026 {
1027 if self.found_name_ref(&name_ref, sink) {
1028 return;
1029 }
1030 }
1031 }
1032 }
1033 }
1034 }
1035
1036 match self.def {
1038 Definition::Module(module) if self.search_self_mod => {
1039 let src = module.definition_source(sema.db);
1040 let file_id = src.file_id.original_file(sema.db);
1041 let (file_id, search_range) = match src.value {
1042 ModuleSource::Module(m) => (file_id, Some(m.syntax().text_range())),
1043 ModuleSource::BlockExpr(b) => (file_id, Some(b.syntax().text_range())),
1044 ModuleSource::SourceFile(_) => (file_id, None),
1045 };
1046
1047 let search_range = if let Some(&range) = search_scope.entries.get(&file_id) {
1048 match (range, search_range) {
1049 (None, range) | (range, None) => range,
1050 (Some(range), Some(search_range)) => match range.intersect(search_range) {
1051 Some(range) => Some(range),
1052 None => return,
1053 },
1054 }
1055 } else {
1056 return;
1057 };
1058
1059 let file_text = sema.db.file_text(file_id.file_id(self.sema.db));
1060 let text = file_text.text(sema.db);
1061 let search_range =
1062 search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
1063
1064 let tree = LazyCell::new(|| sema.parse(file_id).syntax().clone());
1065 let finder = &Finder::new("self");
1066
1067 for offset in Self::match_indices(text, finder, search_range) {
1068 for name_ref in Self::find_nodes(sema, "self", file_id, &tree, offset)
1069 .filter_map(ast::NameRef::cast)
1070 {
1071 if self.found_self_module_name_ref(&name_ref, sink) {
1072 return;
1073 }
1074 }
1075 }
1076 }
1077 _ => {}
1078 }
1079 }
1080
1081 fn found_self_ty_name_ref(
1082 &self,
1083 self_ty: &hir::Type<'_>,
1084 name_ref: &ast::NameRef,
1085 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1086 ) -> bool {
1087 let ty_eq = |ty: hir::Type<'_>| match (ty.as_adt(), self_ty.as_adt()) {
1089 (Some(ty), Some(self_ty)) => ty == self_ty,
1090 (None, None) => ty == *self_ty,
1091 _ => false,
1092 };
1093
1094 match NameRefClass::classify(self.sema, name_ref) {
1095 Some(NameRefClass::Definition(Definition::SelfType(impl_), _))
1096 if ty_eq(impl_.self_ty(self.sema.db)) =>
1097 {
1098 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1099 let reference = FileReference {
1100 range,
1101 name: FileReferenceNode::NameRef(name_ref.clone()),
1102 category: ReferenceCategory::empty(),
1103 };
1104 sink(file_id, reference)
1105 }
1106 _ => false,
1107 }
1108 }
1109
1110 fn found_self_module_name_ref(
1111 &self,
1112 name_ref: &ast::NameRef,
1113 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1114 ) -> bool {
1115 match NameRefClass::classify(self.sema, name_ref) {
1116 Some(NameRefClass::Definition(def @ Definition::Module(_), _)) if def == self.def => {
1117 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1118 let category = if is_name_ref_in_import(name_ref) {
1119 ReferenceCategory::IMPORT
1120 } else {
1121 ReferenceCategory::empty()
1122 };
1123 let reference = FileReference {
1124 range,
1125 name: FileReferenceNode::NameRef(name_ref.clone()),
1126 category,
1127 };
1128 sink(file_id, reference)
1129 }
1130 _ => false,
1131 }
1132 }
1133
1134 fn found_format_args_ref(
1135 &self,
1136 file_id: EditionedFileId,
1137 range: TextRange,
1138 token: ast::String,
1139 res: Either<PathResolution, InlineAsmOperand>,
1140 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1141 ) -> bool {
1142 let def = res.either(Definition::from, Definition::from);
1143 if def == self.def {
1144 let reference = FileReference {
1145 range,
1146 name: FileReferenceNode::FormatStringEntry(token, range),
1147 category: ReferenceCategory::READ,
1148 };
1149 sink(file_id, reference)
1150 } else {
1151 false
1152 }
1153 }
1154
1155 fn found_lifetime(
1156 &self,
1157 lifetime: &ast::Lifetime,
1158 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1159 ) -> bool {
1160 match NameRefClass::classify_lifetime(self.sema, lifetime) {
1161 Some(NameRefClass::Definition(def, _)) if def == self.def => {
1162 let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
1163 let reference = FileReference {
1164 range,
1165 name: FileReferenceNode::Lifetime(lifetime.clone()),
1166 category: ReferenceCategory::empty(),
1167 };
1168 sink(file_id, reference)
1169 }
1170 _ => false,
1171 }
1172 }
1173
1174 fn found_name_ref(
1175 &self,
1176 name_ref: &ast::NameRef,
1177 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1178 ) -> bool {
1179 match NameRefClass::classify(self.sema, name_ref) {
1180 Some(NameRefClass::Definition(def, _))
1181 if self.def == def
1182 || matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_)))
1184 && convert_to_def_in_trait(self.sema.db, def) == self.def =>
1185 {
1186 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1187 let reference = FileReference {
1188 range,
1189 name: FileReferenceNode::NameRef(name_ref.clone()),
1190 category: ReferenceCategory::new(self.sema, &def, name_ref),
1191 };
1192 sink(file_id, reference)
1193 }
1194 Some(NameRefClass::Definition(def, _))
1197 if self.assoc_item_container.is_some()
1198 && matches!(self.def, Definition::TypeAlias(_))
1199 && convert_to_def_in_trait(self.sema.db, def)
1200 == convert_to_def_in_trait(self.sema.db, self.def) =>
1201 {
1202 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1203 let reference = FileReference {
1204 range,
1205 name: FileReferenceNode::NameRef(name_ref.clone()),
1206 category: ReferenceCategory::new(self.sema, &def, name_ref),
1207 };
1208 sink(file_id, reference)
1209 }
1210 Some(NameRefClass::Definition(def, _)) if self.include_self_kw_refs.is_some() => {
1211 if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
1212 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1213 let reference = FileReference {
1214 range,
1215 name: FileReferenceNode::NameRef(name_ref.clone()),
1216 category: ReferenceCategory::new(self.sema, &def, name_ref),
1217 };
1218 sink(file_id, reference)
1219 } else {
1220 false
1221 }
1222 }
1223 Some(NameRefClass::FieldShorthand {
1224 local_ref: local,
1225 field_ref: field,
1226 adt_subst: _,
1227 }) => {
1228 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1229
1230 let field = Definition::Field(field);
1231 let local = Definition::Local(local);
1232 let access = match self.def {
1233 Definition::Field(_) if field == self.def => {
1234 ReferenceCategory::new(self.sema, &field, name_ref)
1235 }
1236 Definition::Local(_) if local == self.def => {
1237 ReferenceCategory::new(self.sema, &local, name_ref)
1238 }
1239 _ => return false,
1240 };
1241 let reference = FileReference {
1242 range,
1243 name: FileReferenceNode::NameRef(name_ref.clone()),
1244 category: access,
1245 };
1246 sink(file_id, reference)
1247 }
1248 _ => false,
1249 }
1250 }
1251
1252 fn found_name(
1253 &self,
1254 name: &ast::Name,
1255 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1256 ) -> bool {
1257 match NameClass::classify(self.sema, name) {
1258 Some(NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ })
1259 if matches!(
1260 self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
1261 ) =>
1262 {
1263 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1264 let reference = FileReference {
1265 range,
1266 name: FileReferenceNode::Name(name.clone()),
1267 category: ReferenceCategory::READ,
1269 };
1270 sink(file_id, reference)
1271 }
1272 Some(NameClass::ConstReference(def)) if self.def == def => {
1273 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1274 let reference = FileReference {
1275 range,
1276 name: FileReferenceNode::Name(name.clone()),
1277 category: ReferenceCategory::empty(),
1278 };
1279 sink(file_id, reference)
1280 }
1281 Some(NameClass::Definition(def)) if def != self.def => {
1282 match (&self.assoc_item_container, self.def) {
1283 (Some(_), Definition::TypeAlias(_))
1286 if convert_to_def_in_trait(self.sema.db, def)
1287 != convert_to_def_in_trait(self.sema.db, self.def) =>
1288 {
1289 return false;
1290 }
1291 (Some(_), Definition::TypeAlias(_)) => {}
1292 (Some(hir::AssocItemContainer::Trait(_)), _)
1295 if convert_to_def_in_trait(self.sema.db, def) == self.def => {}
1296 _ => return false,
1297 }
1298 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1299 let reference = FileReference {
1300 range,
1301 name: FileReferenceNode::Name(name.clone()),
1302 category: ReferenceCategory::empty(),
1303 };
1304 sink(file_id, reference)
1305 }
1306 _ => false,
1307 }
1308 }
1309}
1310
1311fn def_to_ty<'db>(sema: &Semantics<'db, RootDatabase>, def: &Definition) -> Option<hir::Type<'db>> {
1312 match def {
1313 Definition::Adt(adt) => Some(adt.ty(sema.db)),
1314 Definition::TypeAlias(it) => Some(it.ty(sema.db)),
1315 Definition::BuiltinType(it) => Some(it.ty(sema.db)),
1316 Definition::SelfType(it) => Some(it.self_ty(sema.db)),
1317 _ => None,
1318 }
1319}
1320
1321impl ReferenceCategory {
1322 fn new(
1323 sema: &Semantics<'_, RootDatabase>,
1324 def: &Definition,
1325 r: &ast::NameRef,
1326 ) -> ReferenceCategory {
1327 let mut result = ReferenceCategory::empty();
1328 if is_name_ref_in_test(sema, r) {
1329 result |= ReferenceCategory::TEST;
1330 }
1331
1332 if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
1334 if is_name_ref_in_import(r) {
1335 result |= ReferenceCategory::IMPORT;
1336 }
1337 return result;
1338 }
1339
1340 let mode = r.syntax().ancestors().find_map(|node| {
1341 match_ast! {
1342 match node {
1343 ast::BinExpr(expr) => {
1344 if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
1345 if let Some(lhs) = expr.lhs()
1348 && lhs.syntax().text_range().end() == r.syntax().text_range().end() {
1349 return Some(ReferenceCategory::WRITE)
1350 }
1351 }
1352 Some(ReferenceCategory::READ)
1353 },
1354 _ => None,
1355 }
1356 }
1357 }).unwrap_or(ReferenceCategory::READ);
1358
1359 result | mode
1360 }
1361}
1362
1363fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
1364 name_ref
1365 .syntax()
1366 .parent()
1367 .and_then(ast::PathSegment::cast)
1368 .and_then(|it| it.parent_path().top_path().syntax().parent())
1369 .is_some_and(|it| it.kind() == SyntaxKind::USE_TREE)
1370}
1371
1372fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool {
1373 name_ref.syntax().ancestors().any(|node| match ast::Fn::cast(node) {
1374 Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)),
1375 None => false,
1376 })
1377}