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