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