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 if let Some(vis) = self.visibility(db) {
391 return match vis {
392 Visibility::Module(module, _) => {
393 SearchScope::module_and_children(db, module.into())
394 }
395 Visibility::PubCrate(krate) => SearchScope::krate(db, krate.into()),
396 Visibility::Public => SearchScope::reverse_dependencies(db, module.krate()),
397 };
398 }
399
400 let range = match module_source {
401 ModuleSource::Module(m) => Some(m.syntax().text_range()),
402 ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()),
403 ModuleSource::SourceFile(_) => None,
404 };
405 match range {
406 Some(range) => SearchScope::file_range(FileRange { file_id, range }),
407 None => SearchScope::single_file(file_id),
408 }
409 }
410
411 pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> {
412 FindUsages {
413 def: self,
414 rename: None,
415 assoc_item_container: self.as_assoc_item(sema.db).map(|a| a.container(sema.db)),
416 sema,
417 scope: None,
418 include_self_kw_refs: None,
419 search_self_mod: false,
420 }
421 }
422}
423
424#[derive(Clone)]
425pub struct FindUsages<'a> {
426 def: Definition,
427 rename: Option<&'a Rename>,
428 sema: &'a Semantics<'a, RootDatabase>,
429 scope: Option<&'a SearchScope>,
430 assoc_item_container: Option<hir::AssocItemContainer>,
432 include_self_kw_refs: Option<hir::Type<'a>>,
434 search_self_mod: bool,
436}
437
438impl<'a> FindUsages<'a> {
439 pub fn include_self_refs(mut self) -> Self {
441 self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
442 self.search_self_mod = true;
443 self
444 }
445
446 pub fn in_scope(self, scope: &'a SearchScope) -> Self {
448 self.set_scope(Some(scope))
449 }
450
451 pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self {
453 assert!(self.scope.is_none());
454 self.scope = scope;
455 self
456 }
457
458 pub fn with_rename(mut self, rename: Option<&'a Rename>) -> Self {
462 self.rename = rename;
463 self
464 }
465
466 pub fn at_least_one(&self) -> bool {
467 let mut found = false;
468 self.search(&mut |_, _| {
469 found = true;
470 true
471 });
472 found
473 }
474
475 pub fn all(self) -> UsageSearchResult {
476 let mut res = UsageSearchResult::default();
477 self.search(&mut |file_id, reference| {
478 res.references.entry(file_id).or_default().push(reference);
479 false
480 });
481 res
482 }
483
484 fn scope_files<'b>(
485 db: &'b RootDatabase,
486 scope: &'b SearchScope,
487 ) -> impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)> + 'b {
488 scope.entries.iter().map(|(&file_id, &search_range)| {
489 let text = db.file_text(file_id.file_id(db)).text(db);
490 let search_range =
491 search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
492
493 (text.clone(), file_id, search_range)
494 })
495 }
496
497 fn match_indices<'b>(
498 text: &'b str,
499 finder: &'b Finder<'b>,
500 search_range: TextRange,
501 ) -> impl Iterator<Item = TextSize> + 'b {
502 finder.find_iter(text.as_bytes()).filter_map(move |idx| {
503 let offset: TextSize = idx.try_into().unwrap();
504 if !search_range.contains_inclusive(offset) {
505 return None;
506 }
507 if text[..idx]
511 .chars()
512 .next_back()
513 .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_'))
514 || text[idx + finder.needle().len()..]
515 .chars()
516 .next()
517 .is_some_and(|ch| matches!(ch, 'A'..='Z' | 'a'..='z' | '_' | '0'..='9'))
518 {
519 return None;
520 }
521 Some(offset)
522 })
523 }
524
525 fn find_nodes<'b>(
526 sema: &'b Semantics<'_, RootDatabase>,
527 name: &str,
528 file_id: EditionedFileId,
529 node: &syntax::SyntaxNode,
530 offset: TextSize,
531 ) -> impl Iterator<Item = SyntaxNode> + 'b {
532 node.token_at_offset(offset)
533 .find(|it| {
534 it.text().trim_start_matches('\'').trim_start_matches("r#") == name
536 })
537 .into_iter()
538 .flat_map(move |token| {
539 if sema.is_inside_macro_call(InFile::new(file_id.into(), &token)) {
540 sema.descend_into_macros_exact(token)
541 } else {
542 <_>::from([token])
543 }
544 .into_iter()
545 .filter_map(|it| it.parent())
546 })
547 }
548
549 fn short_associated_function_fast_search(
565 &self,
566 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
567 search_scope: &SearchScope,
568 name: &str,
569 ) -> bool {
570 if self.scope.is_some() {
571 return false;
572 }
573
574 let _p = tracing::info_span!("short_associated_function_fast_search").entered();
575
576 let container = (|| {
577 let Definition::Function(function) = self.def else {
578 return None;
579 };
580 if function.has_self_param(self.sema.db) {
581 return None;
582 }
583 match function.container(self.sema.db) {
584 ItemContainer::Impl(impl_) => {
587 let has_trait = impl_.trait_(self.sema.db).is_some();
588 if has_trait {
589 return None;
590 }
591 let adt = impl_.self_ty(self.sema.db).as_adt()?;
592 Some(adt)
593 }
594 _ => None,
595 }
596 })();
597 let Some(container) = container else {
598 return false;
599 };
600
601 fn has_any_name(node: &SyntaxNode, mut predicate: impl FnMut(&str) -> bool) -> bool {
602 node.descendants().any(|node| {
603 match_ast! {
604 match node {
605 ast::Name(it) => predicate(it.text().trim_start_matches("r#")),
606 ast::NameRef(it) => predicate(it.text().trim_start_matches("r#")),
607 _ => false
608 }
609 }
610 })
611 }
612
613 fn collect_possible_aliases(
618 sema: &Semantics<'_, RootDatabase>,
619 container: Adt,
620 ) -> Option<(FxHashSet<SmolStr>, Vec<FileRangeWrapper<EditionedFileId>>)> {
621 fn insert_type_alias(
622 db: &RootDatabase,
623 to_process: &mut Vec<(SmolStr, SearchScope)>,
624 alias_name: &str,
625 def: Definition,
626 ) {
627 let alias = alias_name.trim_start_matches("r#").to_smolstr();
628 tracing::debug!("found alias: {alias}");
629 to_process.push((alias, def.search_scope(db)));
630 }
631
632 let _p = tracing::info_span!("collect_possible_aliases").entered();
633
634 let db = sema.db;
635 let container_name = container.name(db).as_str().to_smolstr();
636 let search_scope = Definition::from(container).search_scope(db);
637 let mut seen = FxHashSet::default();
638 let mut completed = FxHashSet::default();
639 let mut to_process = vec![(container_name, search_scope)];
640 let mut is_possibly_self = Vec::new();
641 let mut total_files_searched = 0;
642
643 while let Some((current_to_process, current_to_process_search_scope)) = to_process.pop()
644 {
645 let is_alias = |alias: &ast::TypeAlias| {
646 let def = sema.to_def(alias)?;
647 let ty = def.ty(db);
648 let is_alias = ty.as_adt()? == container;
649 is_alias.then_some(def)
650 };
651
652 let finder = Finder::new(current_to_process.as_bytes());
653 for (file_text, file_id, search_range) in
654 FindUsages::scope_files(db, ¤t_to_process_search_scope)
655 {
656 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
657
658 for offset in FindUsages::match_indices(&file_text, &finder, search_range) {
659 let usages = FindUsages::find_nodes(
660 sema,
661 ¤t_to_process,
662 file_id,
663 &tree,
664 offset,
665 )
666 .filter(|it| matches!(it.kind(), SyntaxKind::NAME | SyntaxKind::NAME_REF));
667 for usage in usages {
668 if let Some(alias) = usage.parent().and_then(|it| {
669 let path = ast::PathSegment::cast(it)?.parent_path();
670 let use_tree = ast::UseTree::cast(path.syntax().parent()?)?;
671 use_tree.rename()?.name()
672 }) {
673 if seen.insert(InFileWrapper::new(
674 file_id,
675 alias.syntax().text_range(),
676 )) {
677 tracing::debug!("found alias: {alias}");
678 cov_mark::hit!(container_use_rename);
679 to_process.push((
681 alias.text().to_smolstr(),
682 current_to_process_search_scope.clone(),
683 ));
684 }
685 } else if let Some(alias) =
686 usage.ancestors().find_map(ast::TypeAlias::cast)
687 && let Some(name) = alias.name()
688 && seen
689 .insert(InFileWrapper::new(file_id, name.syntax().text_range()))
690 {
691 if let Some(def) = is_alias(&alias) {
692 cov_mark::hit!(container_type_alias);
693 insert_type_alias(
694 sema.db,
695 &mut to_process,
696 name.text().as_str(),
697 def.into(),
698 );
699 } else {
700 cov_mark::hit!(same_name_different_def_type_alias);
701 }
702 }
703
704 let impl_ = 'impl_: {
706 for ancestor in usage.ancestors() {
707 if let Some(parent) = ancestor.parent()
708 && let Some(parent) = ast::Impl::cast(parent)
709 {
710 if matches!(
712 ancestor.kind(),
713 SyntaxKind::ASSOC_ITEM_LIST
714 | SyntaxKind::WHERE_CLAUSE
715 | SyntaxKind::GENERIC_PARAM_LIST
716 ) {
717 break;
718 }
719 if parent
720 .trait_()
721 .is_some_and(|trait_| *trait_.syntax() == ancestor)
722 {
723 break;
724 }
725
726 break 'impl_ Some(parent);
728 }
729 }
730 None
731 };
732 (|| {
733 let impl_ = impl_?;
734 is_possibly_self.push(sema.original_range(impl_.syntax()));
735 let assoc_items = impl_.assoc_item_list()?;
736 let type_aliases = assoc_items
737 .syntax()
738 .descendants()
739 .filter_map(ast::TypeAlias::cast);
740 for type_alias in type_aliases {
741 let Some(ty) = type_alias.ty() else { continue };
742 let Some(name) = type_alias.name() else { continue };
743 let contains_self = ty
744 .syntax()
745 .descendants_with_tokens()
746 .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
747 if !contains_self {
748 continue;
749 }
750 if seen.insert(InFileWrapper::new(
751 file_id,
752 name.syntax().text_range(),
753 )) {
754 if let Some(def) = is_alias(&type_alias) {
755 cov_mark::hit!(self_type_alias);
756 insert_type_alias(
757 sema.db,
758 &mut to_process,
759 name.text().as_str(),
760 def.into(),
761 );
762 } else {
763 cov_mark::hit!(same_name_different_def_type_alias);
764 }
765 }
766 }
767 Some(())
768 })();
769 }
770 }
771 }
772
773 completed.insert(current_to_process);
774
775 total_files_searched += current_to_process_search_scope.entries.len();
776 if total_files_searched > 20_000 && completed.len() > 100 {
778 tracing::info!(aliases_count = %completed.len(), "too much aliases; leaving fast path");
783 return None;
784 }
785 }
786
787 is_possibly_self.sort_unstable_by_key(|position| {
789 (position.file_id, position.range.start(), Reverse(position.range.end()))
790 });
791 is_possibly_self.dedup_by(|pos2, pos1| {
792 pos1.file_id == pos2.file_id
793 && pos1.range.start() <= pos2.range.start()
794 && pos1.range.end() >= pos2.range.end()
795 });
796
797 tracing::info!(aliases_count = %completed.len(), "aliases search completed");
798
799 Some((completed, is_possibly_self))
800 }
801
802 fn search(
803 this: &FindUsages<'_>,
804 finder: &Finder<'_>,
805 name: &str,
806 files: impl Iterator<Item = (Arc<str>, EditionedFileId, TextRange)>,
807 mut container_predicate: impl FnMut(
808 &SyntaxNode,
809 InFileWrapper<EditionedFileId, TextRange>,
810 ) -> bool,
811 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
812 ) {
813 for (file_text, file_id, search_range) in files {
814 let tree = LazyCell::new(move || this.sema.parse(file_id).syntax().clone());
815
816 for offset in FindUsages::match_indices(&file_text, finder, search_range) {
817 let usages = FindUsages::find_nodes(this.sema, name, file_id, &tree, offset)
818 .filter_map(ast::NameRef::cast);
819 for usage in usages {
820 let found_usage = usage
821 .syntax()
822 .parent()
823 .and_then(ast::PathSegment::cast)
824 .map(|path_segment| {
825 container_predicate(
826 path_segment.parent_path().syntax(),
827 InFileWrapper::new(file_id, usage.syntax().text_range()),
828 )
829 })
830 .unwrap_or(false);
831 if found_usage {
832 this.found_name_ref(&usage, sink);
833 }
834 }
835 }
836 }
837 }
838
839 let Some((container_possible_aliases, is_possibly_self)) =
840 collect_possible_aliases(self.sema, container)
841 else {
842 return false;
843 };
844
845 cov_mark::hit!(short_associated_function_fast_search);
846
847 let finder = Finder::new(name.as_bytes());
850 let mut self_positions = FxHashSet::default();
852 tracing::info_span!("Self_search").in_scope(|| {
853 search(
854 self,
855 &finder,
856 name,
857 is_possibly_self.into_iter().map(|position| {
858 (position.file_text(self.sema.db).clone(), position.file_id, position.range)
859 }),
860 |path, name_position| {
861 let has_self = path
862 .descendants_with_tokens()
863 .any(|node| node.kind() == SyntaxKind::SELF_TYPE_KW);
864 if has_self {
865 self_positions.insert(name_position);
866 }
867 has_self
868 },
869 sink,
870 )
871 });
872 tracing::info_span!("aliases_search").in_scope(|| {
873 search(
874 self,
875 &finder,
876 name,
877 FindUsages::scope_files(self.sema.db, search_scope),
878 |path, name_position| {
879 has_any_name(path, |name| container_possible_aliases.contains(name))
880 && !self_positions.contains(&name_position)
881 },
882 sink,
883 )
884 });
885
886 true
887 }
888
889 pub fn search(&self, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool) {
890 let _p = tracing::info_span!("FindUsages:search").entered();
891 let sema = self.sema;
892
893 let search_scope = {
894 let base =
896 as_trait_assoc_def(sema.db, self.def).unwrap_or(self.def).search_scope(sema.db);
897 match &self.scope {
898 None => base,
899 Some(scope) => base.intersection(scope),
900 }
901 };
902
903 let name = match (self.rename, self.def) {
904 (Some(rename), _) => {
905 if rename.underscore_token().is_some() {
906 None
907 } else {
908 rename.name().map(|n| n.to_smolstr())
909 }
910 }
911 (_, Definition::Module(module)) if module.is_crate_root() => {
913 module
918 .krate()
919 .display_name(self.sema.db)
920 .map(|crate_name| crate_name.crate_name().symbol().as_str().into())
921 }
922 _ => {
923 let self_kw_refs = || {
924 self.include_self_kw_refs.as_ref().and_then(|ty| {
925 ty.as_adt()
926 .map(|adt| adt.name(self.sema.db))
927 .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
928 })
929 };
930 self.def
934 .name(sema.db)
935 .or_else(self_kw_refs)
936 .map(|it| it.as_str().trim_start_matches('\'').to_smolstr())
937 }
938 };
939 let name = match &name {
940 Some(s) => s.as_str(),
941 None => return,
942 };
943
944 if name.len() <= 7 && self.short_associated_function_fast_search(sink, &search_scope, name)
946 {
947 return;
948 }
949
950 let finder = &Finder::new(name);
951 let include_self_kw_refs =
952 self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
953 for (text, file_id, search_range) in Self::scope_files(sema.db, &search_scope) {
954 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
955
956 for offset in Self::match_indices(&text, finder, search_range) {
958 let ret = tree.token_at_offset(offset).any(|token| {
959 if let Some((range, _frange, string_token, Some(nameres))) =
960 sema.check_for_format_args_template(token.clone(), offset)
961 {
962 return self.found_format_args_ref(
963 file_id,
964 range,
965 string_token,
966 nameres,
967 sink,
968 );
969 }
970 false
971 });
972 if ret {
973 return;
974 }
975
976 for name in Self::find_nodes(sema, name, file_id, &tree, offset)
977 .filter_map(ast::NameLike::cast)
978 {
979 if match name {
980 ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
981 ast::NameLike::Name(name) => self.found_name(&name, sink),
982 ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
983 } {
984 return;
985 }
986 }
987 }
988 if let Some((self_ty, finder)) = &include_self_kw_refs {
990 for offset in Self::match_indices(&text, finder, search_range) {
991 for name_ref in Self::find_nodes(sema, "Self", file_id, &tree, offset)
992 .filter_map(ast::NameRef::cast)
993 {
994 if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
995 return;
996 }
997 }
998 }
999 }
1000 }
1001
1002 if let Definition::Module(module) = self.def {
1004 let scope =
1005 search_scope.intersection(&SearchScope::module_and_children(self.sema.db, module));
1006
1007 let is_crate_root = module.is_crate_root().then(|| Finder::new("crate"));
1008 let finder = &Finder::new("super");
1009
1010 for (text, file_id, search_range) in Self::scope_files(sema.db, &scope) {
1011 self.sema.db.unwind_if_revision_cancelled();
1012
1013 let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
1014
1015 for offset in Self::match_indices(&text, finder, search_range) {
1016 for name_ref in Self::find_nodes(sema, "super", file_id, &tree, offset)
1017 .filter_map(ast::NameRef::cast)
1018 {
1019 if self.found_name_ref(&name_ref, sink) {
1020 return;
1021 }
1022 }
1023 }
1024 if let Some(finder) = &is_crate_root {
1025 for offset in Self::match_indices(&text, finder, search_range) {
1026 for name_ref in Self::find_nodes(sema, "crate", file_id, &tree, offset)
1027 .filter_map(ast::NameRef::cast)
1028 {
1029 if self.found_name_ref(&name_ref, sink) {
1030 return;
1031 }
1032 }
1033 }
1034 }
1035 }
1036 }
1037
1038 match self.def {
1040 Definition::Module(module) if self.search_self_mod => {
1041 let src = module.definition_source(sema.db);
1042 let file_id = src.file_id.original_file(sema.db);
1043 let (file_id, search_range) = match src.value {
1044 ModuleSource::Module(m) => (file_id, Some(m.syntax().text_range())),
1045 ModuleSource::BlockExpr(b) => (file_id, Some(b.syntax().text_range())),
1046 ModuleSource::SourceFile(_) => (file_id, None),
1047 };
1048
1049 let search_range = if let Some(&range) = search_scope.entries.get(&file_id) {
1050 match (range, search_range) {
1051 (None, range) | (range, None) => range,
1052 (Some(range), Some(search_range)) => match range.intersect(search_range) {
1053 Some(range) => Some(range),
1054 None => return,
1055 },
1056 }
1057 } else {
1058 return;
1059 };
1060
1061 let file_text = sema.db.file_text(file_id.file_id(self.sema.db));
1062 let text = file_text.text(sema.db);
1063 let search_range =
1064 search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&**text)));
1065
1066 let tree = LazyCell::new(|| sema.parse(file_id).syntax().clone());
1067 let finder = &Finder::new("self");
1068
1069 for offset in Self::match_indices(text, finder, search_range) {
1070 for name_ref in Self::find_nodes(sema, "self", file_id, &tree, offset)
1071 .filter_map(ast::NameRef::cast)
1072 {
1073 if self.found_self_module_name_ref(&name_ref, sink) {
1074 return;
1075 }
1076 }
1077 }
1078 }
1079 _ => {}
1080 }
1081 }
1082
1083 fn found_self_ty_name_ref(
1084 &self,
1085 self_ty: &hir::Type<'_>,
1086 name_ref: &ast::NameRef,
1087 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1088 ) -> bool {
1089 let ty_eq = |ty: hir::Type<'_>| match (ty.as_adt(), self_ty.as_adt()) {
1091 (Some(ty), Some(self_ty)) => ty == self_ty,
1092 (None, None) => ty == *self_ty,
1093 _ => false,
1094 };
1095
1096 match NameRefClass::classify(self.sema, name_ref) {
1097 Some(NameRefClass::Definition(Definition::SelfType(impl_), _))
1098 if ty_eq(impl_.self_ty(self.sema.db)) =>
1099 {
1100 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1101 let reference = FileReference {
1102 range,
1103 name: FileReferenceNode::NameRef(name_ref.clone()),
1104 category: ReferenceCategory::empty(),
1105 };
1106 sink(file_id, reference)
1107 }
1108 _ => false,
1109 }
1110 }
1111
1112 fn found_self_module_name_ref(
1113 &self,
1114 name_ref: &ast::NameRef,
1115 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1116 ) -> bool {
1117 match NameRefClass::classify(self.sema, name_ref) {
1118 Some(NameRefClass::Definition(def @ Definition::Module(_), _)) if def == self.def => {
1119 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1120 let category = if is_name_ref_in_import(name_ref) {
1121 ReferenceCategory::IMPORT
1122 } else {
1123 ReferenceCategory::empty()
1124 };
1125 let reference = FileReference {
1126 range,
1127 name: FileReferenceNode::NameRef(name_ref.clone()),
1128 category,
1129 };
1130 sink(file_id, reference)
1131 }
1132 _ => false,
1133 }
1134 }
1135
1136 fn found_format_args_ref(
1137 &self,
1138 file_id: EditionedFileId,
1139 range: TextRange,
1140 token: ast::String,
1141 res: Either<PathResolution, InlineAsmOperand>,
1142 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1143 ) -> bool {
1144 let def = res.either(Definition::from, Definition::from);
1145 if def == self.def {
1146 let reference = FileReference {
1147 range,
1148 name: FileReferenceNode::FormatStringEntry(token, range),
1149 category: ReferenceCategory::READ,
1150 };
1151 sink(file_id, reference)
1152 } else {
1153 false
1154 }
1155 }
1156
1157 fn found_lifetime(
1158 &self,
1159 lifetime: &ast::Lifetime,
1160 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1161 ) -> bool {
1162 match NameRefClass::classify_lifetime(self.sema, lifetime) {
1163 Some(NameRefClass::Definition(def, _)) if def == self.def => {
1164 let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
1165 let reference = FileReference {
1166 range,
1167 name: FileReferenceNode::Lifetime(lifetime.clone()),
1168 category: ReferenceCategory::empty(),
1169 };
1170 sink(file_id, reference)
1171 }
1172 _ => false,
1173 }
1174 }
1175
1176 fn found_name_ref(
1177 &self,
1178 name_ref: &ast::NameRef,
1179 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1180 ) -> bool {
1181 match NameRefClass::classify(self.sema, name_ref) {
1182 Some(NameRefClass::Definition(def, _))
1183 if self.def == def
1184 || matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_)))
1186 && convert_to_def_in_trait(self.sema.db, def) == self.def =>
1187 {
1188 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1189 let reference = FileReference {
1190 range,
1191 name: FileReferenceNode::NameRef(name_ref.clone()),
1192 category: ReferenceCategory::new(self.sema, &def, name_ref),
1193 };
1194 sink(file_id, reference)
1195 }
1196 Some(NameRefClass::Definition(def, _))
1199 if self.assoc_item_container.is_some()
1200 && matches!(self.def, Definition::TypeAlias(_))
1201 && convert_to_def_in_trait(self.sema.db, def)
1202 == convert_to_def_in_trait(self.sema.db, self.def) =>
1203 {
1204 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1205 let reference = FileReference {
1206 range,
1207 name: FileReferenceNode::NameRef(name_ref.clone()),
1208 category: ReferenceCategory::new(self.sema, &def, name_ref),
1209 };
1210 sink(file_id, reference)
1211 }
1212 Some(NameRefClass::Definition(def, _)) if self.include_self_kw_refs.is_some() => {
1213 if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
1214 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1215 let reference = FileReference {
1216 range,
1217 name: FileReferenceNode::NameRef(name_ref.clone()),
1218 category: ReferenceCategory::new(self.sema, &def, name_ref),
1219 };
1220 sink(file_id, reference)
1221 } else {
1222 false
1223 }
1224 }
1225 Some(NameRefClass::FieldShorthand {
1226 local_ref: local,
1227 field_ref: field,
1228 adt_subst: _,
1229 }) => {
1230 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
1231
1232 let field = Definition::Field(field);
1233 let local = Definition::Local(local);
1234 let access = match self.def {
1235 Definition::Field(_) if field == self.def => {
1236 ReferenceCategory::new(self.sema, &field, name_ref)
1237 }
1238 Definition::Local(_) if local == self.def => {
1239 ReferenceCategory::new(self.sema, &local, name_ref)
1240 }
1241 _ => return false,
1242 };
1243 let reference = FileReference {
1244 range,
1245 name: FileReferenceNode::NameRef(name_ref.clone()),
1246 category: access,
1247 };
1248 sink(file_id, reference)
1249 }
1250 _ => false,
1251 }
1252 }
1253
1254 fn found_name(
1255 &self,
1256 name: &ast::Name,
1257 sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
1258 ) -> bool {
1259 match NameClass::classify(self.sema, name) {
1260 Some(NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ })
1261 if matches!(
1262 self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
1263 ) =>
1264 {
1265 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1266 let reference = FileReference {
1267 range,
1268 name: FileReferenceNode::Name(name.clone()),
1269 category: ReferenceCategory::READ,
1271 };
1272 sink(file_id, reference)
1273 }
1274 Some(NameClass::ConstReference(def)) if self.def == def => {
1275 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1276 let reference = FileReference {
1277 range,
1278 name: FileReferenceNode::Name(name.clone()),
1279 category: ReferenceCategory::empty(),
1280 };
1281 sink(file_id, reference)
1282 }
1283 Some(NameClass::Definition(def)) if def != self.def => {
1284 match (&self.assoc_item_container, self.def) {
1285 (Some(_), Definition::TypeAlias(_))
1288 if convert_to_def_in_trait(self.sema.db, def)
1289 != convert_to_def_in_trait(self.sema.db, self.def) =>
1290 {
1291 return false;
1292 }
1293 (Some(_), Definition::TypeAlias(_)) => {}
1294 (Some(hir::AssocItemContainer::Trait(_)), _)
1297 if convert_to_def_in_trait(self.sema.db, def) == self.def => {}
1298 _ => return false,
1299 }
1300 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
1301 let reference = FileReference {
1302 range,
1303 name: FileReferenceNode::Name(name.clone()),
1304 category: ReferenceCategory::empty(),
1305 };
1306 sink(file_id, reference)
1307 }
1308 _ => false,
1309 }
1310 }
1311}
1312
1313fn def_to_ty<'db>(sema: &Semantics<'db, RootDatabase>, def: &Definition) -> Option<hir::Type<'db>> {
1314 match def {
1315 Definition::Adt(adt) => Some(adt.ty(sema.db)),
1316 Definition::TypeAlias(it) => Some(it.ty(sema.db)),
1317 Definition::BuiltinType(it) => Some(it.ty(sema.db)),
1318 Definition::SelfType(it) => Some(it.self_ty(sema.db)),
1319 _ => None,
1320 }
1321}
1322
1323impl ReferenceCategory {
1324 fn new(
1325 sema: &Semantics<'_, RootDatabase>,
1326 def: &Definition,
1327 r: &ast::NameRef,
1328 ) -> ReferenceCategory {
1329 let mut result = ReferenceCategory::empty();
1330 if is_name_ref_in_test(sema, r) {
1331 result |= ReferenceCategory::TEST;
1332 }
1333
1334 if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
1336 if is_name_ref_in_import(r) {
1337 result |= ReferenceCategory::IMPORT;
1338 }
1339 return result;
1340 }
1341
1342 let mode = r.syntax().ancestors().find_map(|node| {
1343 match_ast! {
1344 match node {
1345 ast::BinExpr(expr) => {
1346 if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
1347 if let Some(lhs) = expr.lhs()
1350 && lhs.syntax().text_range().end() == r.syntax().text_range().end() {
1351 return Some(ReferenceCategory::WRITE)
1352 }
1353 }
1354 Some(ReferenceCategory::READ)
1355 },
1356 _ => None,
1357 }
1358 }
1359 }).unwrap_or(ReferenceCategory::READ);
1360
1361 result | mode
1362 }
1363}
1364
1365fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
1366 name_ref
1367 .syntax()
1368 .parent()
1369 .and_then(ast::PathSegment::cast)
1370 .and_then(|it| it.parent_path().top_path().syntax().parent())
1371 .is_some_and(|it| it.kind() == SyntaxKind::USE_TREE)
1372}
1373
1374fn is_name_ref_in_test(sema: &Semantics<'_, RootDatabase>, name_ref: &ast::NameRef) -> bool {
1375 name_ref.syntax().ancestors().any(|node| match ast::Fn::cast(node) {
1376 Some(it) => sema.to_def(&it).is_some_and(|func| func.is_test(sema.db)),
1377 None => false,
1378 })
1379}