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