1 //! This module implements a reference search.
2 //! First, the element at the cursor position must be either an `ast::Name`
3 //! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4 //! try to resolve the direct tree parent of this element, otherwise we
5 //! already have a definition and just need to get its HIR together with
6 //! some information that is needed for futher steps of searching.
7 //! After that, we collect files that might contain references and look
8 //! for text occurrences of the identifier. If there's an `ast::NameRef`
9 //! at the index that the match starts at and its tree parent is
10 //! resolved to the search element definition, we get a reference.
16 defs::{classify_name, classify_name_ref, Definition},
21 algo::find_node_at_offset,
22 ast::{self, NameOwner},
23 AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
26 use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
28 pub(crate) use self::rename::rename;
30 pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
32 #[derive(Debug, Clone)]
33 pub struct ReferenceSearchResult {
34 declaration: Declaration,
35 references: Vec<Reference>,
38 #[derive(Debug, Clone)]
39 pub struct Declaration {
40 pub nav: NavigationTarget,
41 pub kind: ReferenceKind,
42 pub access: Option<ReferenceAccess>,
45 impl ReferenceSearchResult {
46 pub fn declaration(&self) -> &Declaration {
50 pub fn decl_target(&self) -> &NavigationTarget {
54 pub fn references(&self) -> &[Reference] {
58 /// Total number of references
59 /// At least 1 since all valid references should
60 /// Have a declaration
61 pub fn len(&self) -> usize {
62 self.references.len() + 1
66 // allow turning ReferenceSearchResult into an iterator
68 impl IntoIterator for ReferenceSearchResult {
69 type Item = Reference;
70 type IntoIter = std::vec::IntoIter<Reference>;
72 fn into_iter(mut self) -> Self::IntoIter {
73 let mut v = Vec::with_capacity(self.len());
75 file_range: FileRange {
76 file_id: self.declaration.nav.file_id,
77 range: self.declaration.nav.focus_or_full_range(),
79 kind: self.declaration.kind,
80 access: self.declaration.access,
82 v.append(&mut self.references);
87 pub(crate) fn find_all_refs(
88 sema: &Semantics<RootDatabase>,
89 position: FilePosition,
90 search_scope: Option<SearchScope>,
91 ) -> Option<RangeInfo<ReferenceSearchResult>> {
92 let _p = profile::span("find_all_refs");
93 let syntax = sema.parse(position.file_id).syntax().clone();
95 let (opt_name, search_kind) = if let Some(name) =
96 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
98 (Some(name), ReferenceKind::StructLiteral)
101 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset),
102 ReferenceKind::Other,
106 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
110 .set_scope(search_scope)
113 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
116 let decl_range = def.try_to_nav(sema.db)?.focus_or_full_range();
118 let declaration = Declaration {
119 nav: def.try_to_nav(sema.db)?,
120 kind: ReferenceKind::Other,
121 access: decl_access(&def, &syntax, decl_range),
124 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
128 sema: &Semantics<RootDatabase>,
130 position: FilePosition,
131 opt_name: Option<ast::Name>,
132 ) -> Option<RangeInfo<Definition>> {
133 if let Some(name) = opt_name {
134 let def = classify_name(sema, &name)?.definition(sema.db);
135 let range = name.syntax().text_range();
136 return Some(RangeInfo::new(range, def));
139 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
140 let def = classify_name_ref(sema, &name_ref)?.definition(sema.db);
141 let range = name_ref.syntax().text_range();
142 Some(RangeInfo::new(range, def))
145 fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
147 Definition::Local(_) | Definition::Field(_) => {}
151 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
152 if stmt.initializer().is_some() {
153 let pat = stmt.pat()?;
154 if let ast::Pat::IdentPat(it) = pat {
155 if it.mut_token().is_some() {
156 return Some(ReferenceAccess::Write);
164 fn get_struct_def_name_for_struct_literal_search(
165 sema: &Semantics<RootDatabase>,
167 position: FilePosition,
168 ) -> Option<ast::Name> {
169 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
170 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN {
174 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start())
176 return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
179 .find_node_at_offset_with_descend::<ast::GenericParamList>(
181 left.text_range().start(),
185 return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
194 use expect_test::{expect, Expect};
197 use crate::{fixture, SearchScope};
200 fn test_struct_literal_after_space() {
211 f = Foo {a: Foo::f()};
215 Foo STRUCT FileId(0) 0..26 7..10 Other
217 FileId(0) 101..104 StructLiteral
223 fn test_struct_literal_before_space() {
233 Foo STRUCT FileId(0) 0..13 7..10 Other
235 FileId(0) 41..44 Other
236 FileId(0) 54..57 StructLiteral
242 fn test_struct_literal_with_generic_type() {
252 Foo STRUCT FileId(0) 0..16 7..10 Other
254 FileId(0) 64..67 StructLiteral
260 fn test_struct_literal_for_tuple() {
271 Foo STRUCT FileId(0) 0..16 7..10 Other
273 FileId(0) 54..57 StructLiteral
279 fn test_find_all_refs_for_local() {
294 i IDENT_PAT FileId(0) 24..25 Other Write
296 FileId(0) 50..51 Other Write
297 FileId(0) 54..55 Other Read
298 FileId(0) 76..77 Other Write
299 FileId(0) 94..95 Other Write
305 fn search_filters_by_range() {
318 spam IDENT_PAT FileId(0) 19..23 Other
320 FileId(0) 34..38 Other Read
321 FileId(0) 41..45 Other Read
327 fn test_find_all_refs_for_param_inside() {
330 fn foo(i : u32) -> u32 { i<|> }
333 i IDENT_PAT FileId(0) 7..8 Other
335 FileId(0) 25..26 Other Read
341 fn test_find_all_refs_for_fn_param() {
344 fn foo(i<|> : u32) -> u32 { i }
347 i IDENT_PAT FileId(0) 7..8 Other
349 FileId(0) 25..26 Other Read
355 fn test_find_all_refs_field_name() {
368 spam RECORD_FIELD FileId(0) 17..30 21..25 Other
370 FileId(0) 67..71 Other Read
376 fn test_find_all_refs_impl_item_name() {
385 f FN FileId(0) 27..43 30..31 Other
392 fn test_find_all_refs_enum_var_name() {
402 B VARIANT FileId(0) 22..23 22..23 Other
409 fn test_find_all_refs_two_modules() {
417 let i = foo::Foo { n: 5 };
428 let i = bar::Bar { n: 5 };
439 let i = foo::Foo<|> { n: 5 };
443 Foo STRUCT FileId(1) 17..51 28..31 Other
445 FileId(0) 53..56 StructLiteral
446 FileId(2) 79..82 StructLiteral
451 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
452 // So, there are two references: the first one is a definition of the `foo` module,
453 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
455 fn test_find_all_refs_decl_module() {
464 let i = Foo { n: 5 };
473 foo SOURCE_FILE FileId(1) 0..35 Other
475 FileId(0) 14..17 Other
481 fn test_find_all_refs_super_mod_vis() {
492 let i = Foo { n: 5 };
496 pub(super) struct Foo<|> {
501 Foo STRUCT FileId(2) 0..41 18..21 Other
503 FileId(1) 20..23 Other
504 FileId(1) 47..50 StructLiteral
510 fn test_find_all_refs_with_scope() {
519 fn f() { super::quux(); }
522 fn f() { super::quux(); }
529 quux FN FileId(0) 19..35 26..30 Other
531 FileId(1) 16..20 StructLiteral
532 FileId(2) 16..20 StructLiteral
538 Some(SearchScope::single_file(FileId(2))),
540 quux FN FileId(0) 19..35 26..30 Other
542 FileId(2) 16..20 StructLiteral
548 fn test_find_all_refs_macro_def() {
552 macro_rules! m1<|> { () => (()) }
560 m1 MACRO_CALL FileId(0) 0..46 29..31 Other
562 FileId(0) 63..65 StructLiteral
563 FileId(0) 73..75 StructLiteral
569 fn test_basic_highlight_read_write() {
578 i IDENT_PAT FileId(0) 23..24 Other Write
580 FileId(0) 34..35 Other Write
581 FileId(0) 38..39 Other Read
587 fn test_basic_highlight_field_read_write() {
600 f RECORD_FIELD FileId(0) 15..21 15..16 Other
602 FileId(0) 55..56 Other Read
603 FileId(0) 68..69 Other Write
609 fn test_basic_highlight_decl_no_write() {
618 i IDENT_PAT FileId(0) 19..20 Other
620 FileId(0) 26..27 Other Write
626 fn test_find_struct_function_refs_outside_module() {
633 pub fn new<|>() -> Foo { Foo }
638 let _f = foo::Foo::new();
642 new FN FileId(0) 54..81 61..64 Other
644 FileId(0) 126..129 StructLiteral
650 fn test_find_all_refs_nested_module() {
664 f FN FileId(0) 22..31 25..26 Other
666 FileId(1) 11..12 Other
667 FileId(1) 24..25 StructLiteral
672 fn check(ra_fixture: &str, expect: Expect) {
673 check_with_scope(ra_fixture, None, expect)
676 fn check_with_scope(ra_fixture: &str, search_scope: Option<SearchScope>, expect: Expect) {
677 let (analysis, pos) = fixture::position(ra_fixture);
678 let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
680 let mut actual = String::new();
682 let decl = refs.declaration;
683 format_to!(actual, "{} {:?}", decl.nav.debug_render(), decl.kind);
684 if let Some(access) = decl.access {
685 format_to!(actual, " {:?}", access)
690 for r in &refs.references {
691 format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
692 if let Some(access) = r.access {
693 format_to!(actual, " {:?}", access);
697 expect.assert_eq(&actual)