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