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