• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Implementation of find-usages functionality.
2 //!
3 //! It is based on the standard ide trick: first, we run a fast text search to
4 //! get a super-set of matches. Then, we we confirm each match using precise
5 //! name resolution.
6 
7 use std::mem;
8 
9 use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
10 use hir::{
11     AsAssocItem, DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility,
12 };
13 use memchr::memmem::Finder;
14 use nohash_hasher::IntMap;
15 use once_cell::unsync::Lazy;
16 use parser::SyntaxKind;
17 use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
18 use triomphe::Arc;
19 
20 use crate::{
21     defs::{Definition, NameClass, NameRefClass},
22     traits::{as_trait_assoc_def, convert_to_def_in_trait},
23     RootDatabase,
24 };
25 
26 #[derive(Debug, Default, Clone)]
27 pub struct UsageSearchResult {
28     pub references: IntMap<FileId, Vec<FileReference>>,
29 }
30 
31 impl UsageSearchResult {
is_empty(&self) -> bool32     pub fn is_empty(&self) -> bool {
33         self.references.is_empty()
34     }
35 
len(&self) -> usize36     pub fn len(&self) -> usize {
37         self.references.len()
38     }
39 
iter(&self) -> impl Iterator<Item = (&FileId, &[FileReference])> + '_40     pub fn iter(&self) -> impl Iterator<Item = (&FileId, &[FileReference])> + '_ {
41         self.references.iter().map(|(file_id, refs)| (file_id, &**refs))
42     }
43 
file_ranges(&self) -> impl Iterator<Item = FileRange> + '_44     pub fn file_ranges(&self) -> impl Iterator<Item = FileRange> + '_ {
45         self.references.iter().flat_map(|(&file_id, refs)| {
46             refs.iter().map(move |&FileReference { range, .. }| FileRange { file_id, range })
47         })
48     }
49 }
50 
51 impl IntoIterator for UsageSearchResult {
52     type Item = (FileId, Vec<FileReference>);
53     type IntoIter = <IntMap<FileId, Vec<FileReference>> as IntoIterator>::IntoIter;
54 
into_iter(self) -> Self::IntoIter55     fn into_iter(self) -> Self::IntoIter {
56         self.references.into_iter()
57     }
58 }
59 
60 #[derive(Debug, Clone)]
61 pub struct FileReference {
62     /// The range of the reference in the original file
63     pub range: TextRange,
64     /// The node of the reference in the (macro-)file
65     pub name: ast::NameLike,
66     pub category: Option<ReferenceCategory>,
67 }
68 
69 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
70 pub enum ReferenceCategory {
71     // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
72     // Create
73     Write,
74     Read,
75     Import,
76     // FIXME: Some day should be able to search in doc comments. Would probably
77     // need to switch from enum to bitflags then?
78     // DocComment
79 }
80 
81 /// Generally, `search_scope` returns files that might contain references for the element.
82 /// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
83 /// In some cases, the location of the references is known to within a `TextRange`,
84 /// e.g. for things like local variables.
85 #[derive(Clone, Debug)]
86 pub struct SearchScope {
87     entries: IntMap<FileId, Option<TextRange>>,
88 }
89 
90 impl SearchScope {
new(entries: IntMap<FileId, Option<TextRange>>) -> SearchScope91     fn new(entries: IntMap<FileId, Option<TextRange>>) -> SearchScope {
92         SearchScope { entries }
93     }
94 
95     /// Build a search scope spanning the entire crate graph of files.
crate_graph(db: &RootDatabase) -> SearchScope96     fn crate_graph(db: &RootDatabase) -> SearchScope {
97         let mut entries = IntMap::default();
98 
99         let graph = db.crate_graph();
100         for krate in graph.iter() {
101             let root_file = graph[krate].root_file_id;
102             let source_root_id = db.file_source_root(root_file);
103             let source_root = db.source_root(source_root_id);
104             entries.extend(source_root.iter().map(|id| (id, None)));
105         }
106         SearchScope { entries }
107     }
108 
109     /// Build a search scope spanning all the reverse dependencies of the given crate.
reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope110     fn reverse_dependencies(db: &RootDatabase, of: hir::Crate) -> SearchScope {
111         let mut entries = IntMap::default();
112         for rev_dep in of.transitive_reverse_dependencies(db) {
113             let root_file = rev_dep.root_file(db);
114             let source_root_id = db.file_source_root(root_file);
115             let source_root = db.source_root(source_root_id);
116             entries.extend(source_root.iter().map(|id| (id, None)));
117         }
118         SearchScope { entries }
119     }
120 
121     /// Build a search scope spanning the given crate.
krate(db: &RootDatabase, of: hir::Crate) -> SearchScope122     fn krate(db: &RootDatabase, of: hir::Crate) -> SearchScope {
123         let root_file = of.root_file(db);
124         let source_root_id = db.file_source_root(root_file);
125         let source_root = db.source_root(source_root_id);
126         SearchScope { entries: source_root.iter().map(|id| (id, None)).collect() }
127     }
128 
129     /// Build a search scope spanning the given module and all its submodules.
module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope130     fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
131         let mut entries = IntMap::default();
132 
133         let (file_id, range) = {
134             let InFile { file_id, value } = module.definition_source(db);
135             if let Some((file_id, call_source)) = file_id.original_call_node(db) {
136                 (file_id, Some(call_source.text_range()))
137             } else {
138                 (
139                     file_id.original_file(db),
140                     match value {
141                         ModuleSource::SourceFile(_) => None,
142                         ModuleSource::Module(it) => Some(it.syntax().text_range()),
143                         ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()),
144                     },
145                 )
146             }
147         };
148         entries.insert(file_id, range);
149 
150         let mut to_visit: Vec<_> = module.children(db).collect();
151         while let Some(module) = to_visit.pop() {
152             if let InFile { file_id, value: ModuleSource::SourceFile(_) } =
153                 module.definition_source(db)
154             {
155                 entries.insert(file_id.original_file(db), None);
156             }
157             to_visit.extend(module.children(db));
158         }
159         SearchScope { entries }
160     }
161 
162     /// Build an empty search scope.
empty() -> SearchScope163     pub fn empty() -> SearchScope {
164         SearchScope::new(IntMap::default())
165     }
166 
167     /// Build a empty search scope spanning the given file.
single_file(file: FileId) -> SearchScope168     pub fn single_file(file: FileId) -> SearchScope {
169         SearchScope::new(std::iter::once((file, None)).collect())
170     }
171 
172     /// Build a empty search scope spanning the text range of the given file.
file_range(range: FileRange) -> SearchScope173     pub fn file_range(range: FileRange) -> SearchScope {
174         SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
175     }
176 
177     /// Build a empty search scope spanning the given files.
files(files: &[FileId]) -> SearchScope178     pub fn files(files: &[FileId]) -> SearchScope {
179         SearchScope::new(files.iter().map(|f| (*f, None)).collect())
180     }
181 
intersection(&self, other: &SearchScope) -> SearchScope182     pub fn intersection(&self, other: &SearchScope) -> SearchScope {
183         let (mut small, mut large) = (&self.entries, &other.entries);
184         if small.len() > large.len() {
185             mem::swap(&mut small, &mut large)
186         }
187 
188         let intersect_ranges =
189             |r1: Option<TextRange>, r2: Option<TextRange>| -> Option<Option<TextRange>> {
190                 match (r1, r2) {
191                     (None, r) | (r, None) => Some(r),
192                     (Some(r1), Some(r2)) => r1.intersect(r2).map(Some),
193                 }
194             };
195         let res = small
196             .iter()
197             .filter_map(|(&file_id, &r1)| {
198                 let &r2 = large.get(&file_id)?;
199                 let r = intersect_ranges(r1, r2)?;
200                 Some((file_id, r))
201             })
202             .collect();
203 
204         SearchScope::new(res)
205     }
206 }
207 
208 impl IntoIterator for SearchScope {
209     type Item = (FileId, Option<TextRange>);
210     type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
211 
into_iter(self) -> Self::IntoIter212     fn into_iter(self) -> Self::IntoIter {
213         self.entries.into_iter()
214     }
215 }
216 
217 impl Definition {
search_scope(&self, db: &RootDatabase) -> SearchScope218     fn search_scope(&self, db: &RootDatabase) -> SearchScope {
219         let _p = profile::span("search_scope");
220 
221         if let Definition::BuiltinType(_) = self {
222             return SearchScope::crate_graph(db);
223         }
224 
225         // def is crate root
226         // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name
227         if let &Definition::Module(module) = self {
228             if module.is_crate_root() {
229                 return SearchScope::reverse_dependencies(db, module.krate());
230             }
231         }
232 
233         let module = match self.module(db) {
234             Some(it) => it,
235             None => return SearchScope::empty(),
236         };
237         let InFile { file_id, value: module_source } = module.definition_source(db);
238         let file_id = file_id.original_file(db);
239 
240         if let Definition::Local(var) = self {
241             let def = match var.parent(db) {
242                 DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
243                 DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
244                 DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
245                 DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
246                 // FIXME: implement
247                 DefWithBody::InTypeConst(_) => return SearchScope::empty(),
248             };
249             return match def {
250                 Some(def) => SearchScope::file_range(def.as_ref().original_file_range_full(db)),
251                 None => SearchScope::single_file(file_id),
252             };
253         }
254 
255         if let Definition::SelfType(impl_) = self {
256             return match impl_.source(db).map(|src| src.syntax().cloned()) {
257                 Some(def) => SearchScope::file_range(def.as_ref().original_file_range_full(db)),
258                 None => SearchScope::single_file(file_id),
259             };
260         }
261 
262         if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self {
263             let def = match param.parent(db) {
264                 hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()),
265                 hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()),
266                 hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()),
267                 hir::GenericDef::TraitAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
268                 hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()),
269                 hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()),
270                 hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()),
271                 hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()),
272             };
273             return match def {
274                 Some(def) => SearchScope::file_range(def.as_ref().original_file_range_full(db)),
275                 None => SearchScope::single_file(file_id),
276             };
277         }
278 
279         if let Definition::Macro(macro_def) = self {
280             return match macro_def.kind(db) {
281                 hir::MacroKind::Declarative => {
282                     if macro_def.attrs(db).by_key("macro_export").exists() {
283                         SearchScope::reverse_dependencies(db, module.krate())
284                     } else {
285                         SearchScope::krate(db, module.krate())
286                     }
287                 }
288                 hir::MacroKind::BuiltIn => SearchScope::crate_graph(db),
289                 hir::MacroKind::Derive | hir::MacroKind::Attr | hir::MacroKind::ProcMacro => {
290                     SearchScope::reverse_dependencies(db, module.krate())
291                 }
292             };
293         }
294 
295         if let Definition::DeriveHelper(_) = self {
296             return SearchScope::reverse_dependencies(db, module.krate());
297         }
298 
299         let vis = self.visibility(db);
300         if let Some(Visibility::Public) = vis {
301             return SearchScope::reverse_dependencies(db, module.krate());
302         }
303         if let Some(Visibility::Module(module)) = vis {
304             return SearchScope::module_and_children(db, module.into());
305         }
306 
307         let range = match module_source {
308             ModuleSource::Module(m) => Some(m.syntax().text_range()),
309             ModuleSource::BlockExpr(b) => Some(b.syntax().text_range()),
310             ModuleSource::SourceFile(_) => None,
311         };
312         match range {
313             Some(range) => SearchScope::file_range(FileRange { file_id, range }),
314             None => SearchScope::single_file(file_id),
315         }
316     }
317 
usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a>318     pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> {
319         FindUsages {
320             def: self,
321             assoc_item_container: self.as_assoc_item(sema.db).map(|a| a.container(sema.db)),
322             sema,
323             scope: None,
324             include_self_kw_refs: None,
325             search_self_mod: false,
326         }
327     }
328 }
329 
330 #[derive(Clone)]
331 pub struct FindUsages<'a> {
332     def: Definition,
333     sema: &'a Semantics<'a, RootDatabase>,
334     scope: Option<SearchScope>,
335     /// The container of our definition should it be an assoc item
336     assoc_item_container: Option<hir::AssocItemContainer>,
337     /// whether to search for the `Self` type of the definition
338     include_self_kw_refs: Option<hir::Type>,
339     /// whether to search for the `self` module
340     search_self_mod: bool,
341 }
342 
343 impl<'a> FindUsages<'a> {
344     /// Enable searching for `Self` when the definition is a type or `self` for modules.
include_self_refs(mut self) -> FindUsages<'a>345     pub fn include_self_refs(mut self) -> FindUsages<'a> {
346         self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
347         self.search_self_mod = true;
348         self
349     }
350 
351     /// Limit the search to a given [`SearchScope`].
in_scope(self, scope: SearchScope) -> FindUsages<'a>352     pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
353         self.set_scope(Some(scope))
354     }
355 
356     /// Limit the search to a given [`SearchScope`].
set_scope(mut self, scope: Option<SearchScope>) -> FindUsages<'a>357     pub fn set_scope(mut self, scope: Option<SearchScope>) -> FindUsages<'a> {
358         assert!(self.scope.is_none());
359         self.scope = scope;
360         self
361     }
362 
at_least_one(&self) -> bool363     pub fn at_least_one(&self) -> bool {
364         let mut found = false;
365         self.search(&mut |_, _| {
366             found = true;
367             true
368         });
369         found
370     }
371 
all(self) -> UsageSearchResult372     pub fn all(self) -> UsageSearchResult {
373         let mut res = UsageSearchResult::default();
374         self.search(&mut |file_id, reference| {
375             res.references.entry(file_id).or_default().push(reference);
376             false
377         });
378         res
379     }
380 
search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool)381     fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
382         let _p = profile::span("FindUsages:search");
383         let sema = self.sema;
384 
385         let search_scope = {
386             // FIXME: Is the trait scope needed for trait impl assoc items?
387             let base =
388                 as_trait_assoc_def(sema.db, self.def).unwrap_or(self.def).search_scope(sema.db);
389             match &self.scope {
390                 None => base,
391                 Some(scope) => base.intersection(scope),
392             }
393         };
394 
395         let name = match self.def {
396             // special case crate modules as these do not have a proper name
397             Definition::Module(module) if module.is_crate_root() => {
398                 // FIXME: This assumes the crate name is always equal to its display name when it really isn't
399                 module
400                     .krate()
401                     .display_name(self.sema.db)
402                     .map(|crate_name| crate_name.crate_name().as_smol_str().clone())
403             }
404             _ => {
405                 let self_kw_refs = || {
406                     self.include_self_kw_refs.as_ref().and_then(|ty| {
407                         ty.as_adt()
408                             .map(|adt| adt.name(self.sema.db))
409                             .or_else(|| ty.as_builtin().map(|builtin| builtin.name()))
410                     })
411                 };
412                 // We need to unescape the name in case it is written without "r#" in earlier
413                 // editions of Rust where it isn't a keyword.
414                 self.def.name(sema.db).or_else(self_kw_refs).map(|it| it.unescaped().to_smol_str())
415             }
416         };
417         let name = match &name {
418             Some(s) => s.as_str(),
419             None => return,
420         };
421         let finder = &Finder::new(name);
422         let include_self_kw_refs =
423             self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
424 
425         // for<'a> |text: &'a str, name: &'a str, search_range: TextRange| -> impl Iterator<Item = TextSize> + 'a { ... }
426         fn match_indices<'a>(
427             text: &'a str,
428             finder: &'a Finder<'a>,
429             search_range: TextRange,
430         ) -> impl Iterator<Item = TextSize> + 'a {
431             finder.find_iter(text.as_bytes()).filter_map(move |idx| {
432                 let offset: TextSize = idx.try_into().unwrap();
433                 if !search_range.contains_inclusive(offset) {
434                     return None;
435                 }
436                 Some(offset)
437             })
438         }
439 
440         // for<'a> |scope: &'a SearchScope| -> impl Iterator<Item = (Arc<String>, FileId, TextRange)> + 'a { ... }
441         fn scope_files<'a>(
442             sema: &'a Semantics<'_, RootDatabase>,
443             scope: &'a SearchScope,
444         ) -> impl Iterator<Item = (Arc<str>, FileId, TextRange)> + 'a {
445             scope.entries.iter().map(|(&file_id, &search_range)| {
446                 let text = sema.db.file_text(file_id);
447                 let search_range =
448                     search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
449 
450                 (text, file_id, search_range)
451             })
452         }
453 
454         let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| {
455             node.token_at_offset(offset)
456                 .find(|it| {
457                     // `name` is stripped of raw ident prefix. See the comment on name retrieval above.
458                     it.text().trim_start_matches("r#") == name
459                 })
460                 .into_iter()
461                 .flat_map(|token| {
462                     // FIXME: There should be optimization potential here
463                     // Currently we try to descend everything we find which
464                     // means we call `Semantics::descend_into_macros` on
465                     // every textual hit. That function is notoriously
466                     // expensive even for things that do not get down mapped
467                     // into macros.
468                     sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent())
469                 })
470         };
471 
472         for (text, file_id, search_range) in scope_files(sema, &search_scope) {
473             let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
474 
475             // Search for occurrences of the items name
476             for offset in match_indices(&text, finder, search_range) {
477                 for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) {
478                     if match name {
479                         ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
480                         ast::NameLike::Name(name) => self.found_name(&name, sink),
481                         ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
482                     } {
483                         return;
484                     }
485                 }
486             }
487             // Search for occurrences of the `Self` referring to our type
488             if let Some((self_ty, finder)) = &include_self_kw_refs {
489                 for offset in match_indices(&text, finder, search_range) {
490                     for name_ref in find_nodes("Self", &tree, offset).filter_map(ast::NameRef::cast)
491                     {
492                         if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
493                             return;
494                         }
495                     }
496                 }
497             }
498         }
499 
500         // Search for `super` and `crate` resolving to our module
501         if let Definition::Module(module) = self.def {
502             let scope =
503                 search_scope.intersection(&SearchScope::module_and_children(self.sema.db, module));
504 
505             let is_crate_root = module.is_crate_root().then(|| Finder::new("crate"));
506             let finder = &Finder::new("super");
507 
508             for (text, file_id, search_range) in scope_files(sema, &scope) {
509                 let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
510 
511                 for offset in match_indices(&text, finder, search_range) {
512                     for name_ref in
513                         find_nodes("super", &tree, offset).filter_map(ast::NameRef::cast)
514                     {
515                         if self.found_name_ref(&name_ref, sink) {
516                             return;
517                         }
518                     }
519                 }
520                 if let Some(finder) = &is_crate_root {
521                     for offset in match_indices(&text, finder, search_range) {
522                         for name_ref in
523                             find_nodes("crate", &tree, offset).filter_map(ast::NameRef::cast)
524                         {
525                             if self.found_name_ref(&name_ref, sink) {
526                                 return;
527                             }
528                         }
529                     }
530                 }
531             }
532         }
533 
534         // search for module `self` references in our module's definition source
535         match self.def {
536             Definition::Module(module) if self.search_self_mod => {
537                 let src = module.definition_source(sema.db);
538                 let file_id = src.file_id.original_file(sema.db);
539                 let (file_id, search_range) = match src.value {
540                     ModuleSource::Module(m) => (file_id, Some(m.syntax().text_range())),
541                     ModuleSource::BlockExpr(b) => (file_id, Some(b.syntax().text_range())),
542                     ModuleSource::SourceFile(_) => (file_id, None),
543                 };
544 
545                 let search_range = if let Some(&range) = search_scope.entries.get(&file_id) {
546                     match (range, search_range) {
547                         (None, range) | (range, None) => range,
548                         (Some(range), Some(search_range)) => match range.intersect(search_range) {
549                             Some(range) => Some(range),
550                             None => return,
551                         },
552                     }
553                 } else {
554                     return;
555                 };
556 
557                 let text = sema.db.file_text(file_id);
558                 let search_range =
559                     search_range.unwrap_or_else(|| TextRange::up_to(TextSize::of(&*text)));
560 
561                 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
562                 let finder = &Finder::new("self");
563 
564                 for offset in match_indices(&text, finder, search_range) {
565                     for name_ref in find_nodes("self", &tree, offset).filter_map(ast::NameRef::cast)
566                     {
567                         if self.found_self_module_name_ref(&name_ref, sink) {
568                             return;
569                         }
570                     }
571                 }
572             }
573             _ => {}
574         }
575     }
576 
found_self_ty_name_ref( &self, self_ty: &hir::Type, name_ref: &ast::NameRef, sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool577     fn found_self_ty_name_ref(
578         &self,
579         self_ty: &hir::Type,
580         name_ref: &ast::NameRef,
581         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
582     ) -> bool {
583         match NameRefClass::classify(self.sema, name_ref) {
584             Some(NameRefClass::Definition(Definition::SelfType(impl_)))
585                 if impl_.self_ty(self.sema.db) == *self_ty =>
586             {
587                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
588                 let reference = FileReference {
589                     range,
590                     name: ast::NameLike::NameRef(name_ref.clone()),
591                     category: None,
592                 };
593                 sink(file_id, reference)
594             }
595             _ => false,
596         }
597     }
598 
found_self_module_name_ref( &self, name_ref: &ast::NameRef, sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool599     fn found_self_module_name_ref(
600         &self,
601         name_ref: &ast::NameRef,
602         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
603     ) -> bool {
604         match NameRefClass::classify(self.sema, name_ref) {
605             Some(NameRefClass::Definition(def @ Definition::Module(_))) if def == self.def => {
606                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
607                 let reference = FileReference {
608                     range,
609                     name: ast::NameLike::NameRef(name_ref.clone()),
610                     category: is_name_ref_in_import(name_ref).then_some(ReferenceCategory::Import),
611                 };
612                 sink(file_id, reference)
613             }
614             _ => false,
615         }
616     }
617 
found_lifetime( &self, lifetime: &ast::Lifetime, sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool618     fn found_lifetime(
619         &self,
620         lifetime: &ast::Lifetime,
621         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
622     ) -> bool {
623         match NameRefClass::classify_lifetime(self.sema, lifetime) {
624             Some(NameRefClass::Definition(def)) if def == self.def => {
625                 let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
626                 let reference = FileReference {
627                     range,
628                     name: ast::NameLike::Lifetime(lifetime.clone()),
629                     category: None,
630                 };
631                 sink(file_id, reference)
632             }
633             _ => false,
634         }
635     }
636 
found_name_ref( &self, name_ref: &ast::NameRef, sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool637     fn found_name_ref(
638         &self,
639         name_ref: &ast::NameRef,
640         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
641     ) -> bool {
642         match NameRefClass::classify(self.sema, name_ref) {
643             Some(NameRefClass::Definition(def))
644                 if self.def == def
645                     // is our def a trait assoc item? then we want to find all assoc items from trait impls of our trait
646                     || matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_)))
647                         && convert_to_def_in_trait(self.sema.db, def) == self.def =>
648             {
649                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
650                 let reference = FileReference {
651                     range,
652                     name: ast::NameLike::NameRef(name_ref.clone()),
653                     category: ReferenceCategory::new(&def, name_ref),
654                 };
655                 sink(file_id, reference)
656             }
657             // FIXME: special case type aliases, we can't filter between impl and trait defs here as we lack the substitutions
658             // so we always resolve all assoc type aliases to both their trait def and impl defs
659             Some(NameRefClass::Definition(def))
660                 if self.assoc_item_container.is_some()
661                     && matches!(self.def, Definition::TypeAlias(_))
662                     && convert_to_def_in_trait(self.sema.db, def)
663                         == convert_to_def_in_trait(self.sema.db, self.def) =>
664             {
665                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
666                 let reference = FileReference {
667                     range,
668                     name: ast::NameLike::NameRef(name_ref.clone()),
669                     category: ReferenceCategory::new(&def, name_ref),
670                 };
671                 sink(file_id, reference)
672             }
673             Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => {
674                 if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
675                     let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
676                     let reference = FileReference {
677                         range,
678                         name: ast::NameLike::NameRef(name_ref.clone()),
679                         category: ReferenceCategory::new(&def, name_ref),
680                     };
681                     sink(file_id, reference)
682                 } else {
683                     false
684                 }
685             }
686             Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
687                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
688 
689                 let field = Definition::Field(field);
690                 let local = Definition::Local(local);
691                 let access = match self.def {
692                     Definition::Field(_) if field == self.def => {
693                         ReferenceCategory::new(&field, name_ref)
694                     }
695                     Definition::Local(_) if local == self.def => {
696                         ReferenceCategory::new(&local, name_ref)
697                     }
698                     _ => return false,
699                 };
700                 let reference = FileReference {
701                     range,
702                     name: ast::NameLike::NameRef(name_ref.clone()),
703                     category: access,
704                 };
705                 sink(file_id, reference)
706             }
707             _ => false,
708         }
709     }
710 
found_name( &self, name: &ast::Name, sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool711     fn found_name(
712         &self,
713         name: &ast::Name,
714         sink: &mut dyn FnMut(FileId, FileReference) -> bool,
715     ) -> bool {
716         match NameClass::classify(self.sema, name) {
717             Some(NameClass::PatFieldShorthand { local_def: _, field_ref })
718                 if matches!(
719                     self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
720                 ) =>
721             {
722                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
723                 let reference = FileReference {
724                     range,
725                     name: ast::NameLike::Name(name.clone()),
726                     // FIXME: mutable patterns should have `Write` access
727                     category: Some(ReferenceCategory::Read),
728                 };
729                 sink(file_id, reference)
730             }
731             Some(NameClass::ConstReference(def)) if self.def == def => {
732                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
733                 let reference = FileReference {
734                     range,
735                     name: ast::NameLike::Name(name.clone()),
736                     category: None,
737                 };
738                 sink(file_id, reference)
739             }
740             Some(NameClass::Definition(def)) if def != self.def => {
741                 match (&self.assoc_item_container, self.def) {
742                     // for type aliases we always want to reference the trait def and all the trait impl counterparts
743                     // FIXME: only until we can resolve them correctly, see FIXME above
744                     (Some(_), Definition::TypeAlias(_))
745                         if convert_to_def_in_trait(self.sema.db, def)
746                             != convert_to_def_in_trait(self.sema.db, self.def) =>
747                     {
748                         return false
749                     }
750                     (Some(_), Definition::TypeAlias(_)) => {}
751                     // We looking at an assoc item of a trait definition, so reference all the
752                     // corresponding assoc items belonging to this trait's trait implementations
753                     (Some(hir::AssocItemContainer::Trait(_)), _)
754                         if convert_to_def_in_trait(self.sema.db, def) == self.def => {}
755                     _ => return false,
756                 }
757                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
758                 let reference = FileReference {
759                     range,
760                     name: ast::NameLike::Name(name.clone()),
761                     category: None,
762                 };
763                 sink(file_id, reference)
764             }
765             _ => false,
766         }
767     }
768 }
769 
def_to_ty(sema: &Semantics<'_, RootDatabase>, def: &Definition) -> Option<hir::Type>770 fn def_to_ty(sema: &Semantics<'_, RootDatabase>, def: &Definition) -> Option<hir::Type> {
771     match def {
772         Definition::Adt(adt) => Some(adt.ty(sema.db)),
773         Definition::TypeAlias(it) => Some(it.ty(sema.db)),
774         Definition::BuiltinType(it) => Some(it.ty(sema.db)),
775         Definition::SelfType(it) => Some(it.self_ty(sema.db)),
776         _ => None,
777     }
778 }
779 
780 impl ReferenceCategory {
new(def: &Definition, r: &ast::NameRef) -> Option<ReferenceCategory>781     fn new(def: &Definition, r: &ast::NameRef) -> Option<ReferenceCategory> {
782         // Only Locals and Fields have accesses for now.
783         if !matches!(def, Definition::Local(_) | Definition::Field(_)) {
784             return is_name_ref_in_import(r).then_some(ReferenceCategory::Import);
785         }
786 
787         let mode = r.syntax().ancestors().find_map(|node| {
788         match_ast! {
789             match node {
790                 ast::BinExpr(expr) => {
791                     if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
792                         // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
793                         // FIXME: This is not terribly accurate.
794                         if let Some(lhs) = expr.lhs() {
795                             if lhs.syntax().text_range().end() == r.syntax().text_range().end() {
796                                 return Some(ReferenceCategory::Write);
797                             }
798                         }
799                     }
800                     Some(ReferenceCategory::Read)
801                 },
802                 _ => None
803             }
804         }
805     });
806 
807         // Default Locals and Fields to read
808         mode.or(Some(ReferenceCategory::Read))
809     }
810 }
811 
is_name_ref_in_import(name_ref: &ast::NameRef) -> bool812 fn is_name_ref_in_import(name_ref: &ast::NameRef) -> bool {
813     name_ref
814         .syntax()
815         .parent()
816         .and_then(ast::PathSegment::cast)
817         .and_then(|it| it.parent_path().top_path().syntax().parent())
818         .map_or(false, |it| it.kind() == SyntaxKind::USE_TREE)
819 }
820