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 further 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.
12 use hir::{PathResolution, Semantics};
15 defs::{Definition, NameClass, NameRefClass},
16 search::{ReferenceAccess, SearchScope, UsageSearchResult},
19 use rustc_hash::FxHashMap;
21 algo::find_node_at_offset,
22 ast::{self, NameOwner},
23 match_ast, AstNode, SyntaxNode, TextRange, TextSize, T,
26 use crate::{display::TryToNav, FilePosition, NavigationTarget};
28 #[derive(Debug, Clone)]
29 pub struct ReferenceSearchResult {
30 pub declaration: Option<Declaration>,
31 pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceAccess>)>>,
34 #[derive(Debug, Clone)]
35 pub struct Declaration {
36 pub nav: NavigationTarget,
37 pub access: Option<ReferenceAccess>,
40 // Feature: Find All References
42 // Shows all references of the item at the cursor location
45 // | Editor | Shortcut
47 // | VS Code | kbd:[Shift+Alt+F12]
50 // image::https://user-images.githubusercontent.com/48062697/113020670-b7c34f00-917a-11eb-8003-370ac5f2b3cb.gif[]
51 pub(crate) fn find_all_refs(
52 sema: &Semantics<RootDatabase>,
53 position: FilePosition,
54 search_scope: Option<SearchScope>,
55 ) -> Option<ReferenceSearchResult> {
56 let _p = profile::span("find_all_refs");
57 let syntax = sema.parse(position.file_id).syntax().clone();
59 let mut is_literal_search = false;
60 let def = if let Some(name) = name_for_constructor_search(&syntax, position) {
61 is_literal_search = true;
62 match NameClass::classify(sema, &name)? {
63 NameClass::Definition(it) | NameClass::ConstReference(it) => it,
64 NameClass::PatFieldShorthand { local_def: _, field_ref } => {
65 Definition::Field(field_ref)
69 find_def(sema, &syntax, position.offset)?
72 let mut usages = def.usages(sema).set_scope(search_scope).include_self_refs().all();
73 let declaration = match def {
74 Definition::ModuleDef(hir::ModuleDef::Module(module)) => {
75 Some(NavigationTarget::from_module_to_decl(sema.db, module))
77 def => def.try_to_nav(sema.db),
80 let decl_range = nav.focus_or_full_range();
81 Declaration { nav, access: decl_access(&def, &syntax, decl_range) }
83 if is_literal_search {
84 retain_adt_literal_usages(&mut usages, def, sema);
87 let references = usages
89 .map(|(file_id, refs)| {
90 (file_id, refs.into_iter().map(|file_ref| (file_ref.range, file_ref.access)).collect())
94 Some(ReferenceSearchResult { declaration, references })
97 pub(crate) fn find_def(
98 sema: &Semantics<RootDatabase>,
101 ) -> Option<Definition> {
102 let def = match sema.find_node_at_offset_with_descend(syntax, offset)? {
103 ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
104 NameRefClass::Definition(def) => def,
105 NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
106 Definition::Local(local_ref)
109 ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
110 NameClass::Definition(it) | NameClass::ConstReference(it) => it,
111 NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
112 Definition::Local(local_def)
115 ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
116 .and_then(|class| match class {
117 NameRefClass::Definition(it) => Some(it),
121 NameClass::classify_lifetime(sema, &lifetime).and_then(|class| match class {
122 NameClass::Definition(it) => Some(it),
130 pub(crate) fn decl_access(
134 ) -> Option<ReferenceAccess> {
136 Definition::Local(_) | Definition::Field(_) => {}
140 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
141 if stmt.initializer().is_some() {
142 let pat = stmt.pat()?;
143 if let ast::Pat::IdentPat(it) = pat {
144 if it.mut_token().is_some() {
145 return Some(ReferenceAccess::Write);
153 /// Filter out all non-literal usages for adt-defs
154 fn retain_adt_literal_usages(
155 usages: &mut UsageSearchResult,
157 sema: &Semantics<RootDatabase>,
159 let refs = usages.references.values_mut();
161 Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => {
163 it.retain(|reference| {
167 .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref))
170 usages.references.retain(|_, it| !it.is_empty());
172 Definition::ModuleDef(hir::ModuleDef::Adt(_) | hir::ModuleDef::Variant(_)) => {
174 it.retain(|reference| reference.name.as_name_ref().map_or(false, is_lit_name_ref))
176 usages.references.retain(|_, it| !it.is_empty());
182 /// Returns `Some` if the cursor is at a position for an item to search for all its constructor/literal usages
183 fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
184 let token = syntax.token_at_offset(position.offset).right_biased()?;
185 let token_parent = token.parent()?;
186 let kind = token.kind();
188 ast::Struct::cast(token_parent)
189 .filter(|struct_| struct_.field_list().is_none())
190 .and_then(|struct_| struct_.name())
191 } else if kind == T!['{'] {
194 ast::RecordFieldList(rfl) => match_ast! {
195 match (rfl.syntax().parent()?) {
196 ast::Variant(it) => it.name(),
197 ast::Struct(it) => it.name(),
198 ast::Union(it) => it.name(),
202 ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
206 } else if kind == T!['('] {
207 let tfl = ast::TupleFieldList::cast(token_parent)?;
209 match (tfl.syntax().parent()?) {
210 ast::Variant(it) => it.name(),
211 ast::Struct(it) => it.name(),
220 fn is_enum_lit_name_ref(
221 sema: &Semantics<RootDatabase>,
223 name_ref: &ast::NameRef,
225 let path_is_variant_of_enum = |path: ast::Path| {
227 sema.resolve_path(&path),
228 Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
229 if variant.parent_enum(sema.db) == enum_
235 .find_map(|ancestor| {
238 ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum),
239 ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum),
247 fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool {
248 path.and_then(|path| path.segment())
249 .and_then(|segment| segment.name_ref())
250 .map_or(false, |segment| segment == *name_ref)
253 fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
254 name_ref.syntax().ancestors().find_map(|ancestor| {
257 ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)),
258 ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)),
267 use expect_test::{expect, Expect};
268 use ide_db::base_db::FileId;
271 use crate::{fixture, SearchScope};
274 fn test_struct_literal_after_space() {
285 f = Foo {a: Foo::f()};
289 Foo Struct FileId(0) 0..26 7..10
297 fn test_struct_literal_before_space() {
307 Foo Struct FileId(0) 0..13 7..10
316 fn test_struct_literal_with_generic_type() {
326 Foo Struct FileId(0) 0..16 7..10
334 fn test_struct_literal_for_tuple() {
345 Foo Struct FileId(0) 0..16 7..10
353 fn test_struct_literal_for_union() {
366 Foo Union FileId(0) 0..24 6..9
374 fn test_enum_after_space() {
390 Foo Enum FileId(0) 0..37 5..8
400 fn test_variant_record_after_space() {
410 f = Foo::A { n: 92 };
414 A Variant FileId(0) 15..27 15..16
421 fn test_variant_tuple_before_paren() {
435 A Variant FileId(0) 15..21 15..16
443 fn test_enum_before_space() {
456 Foo Enum FileId(0) 0..26 5..8
465 fn test_enum_with_generic_type() {
478 Foo Enum FileId(0) 0..32 5..8
486 fn test_enum_for_tuple() {
499 Foo Enum FileId(0) 0..33 5..8
507 fn test_find_all_refs_for_local() {
522 i Local FileId(0) 20..25 24..25 Write
524 FileId(0) 50..51 Write
525 FileId(0) 54..55 Read
526 FileId(0) 76..77 Write
527 FileId(0) 94..95 Write
533 fn search_filters_by_range() {
546 spam Local FileId(0) 19..23 19..23
548 FileId(0) 34..38 Read
549 FileId(0) 41..45 Read
555 fn test_find_all_refs_for_param_inside() {
558 fn foo(i : u32) -> u32 { i$0 }
561 i ValueParam FileId(0) 7..8 7..8
563 FileId(0) 25..26 Read
569 fn test_find_all_refs_for_fn_param() {
572 fn foo(i$0 : u32) -> u32 { i }
575 i ValueParam FileId(0) 7..8 7..8
577 FileId(0) 25..26 Read
583 fn test_find_all_refs_field_name() {
596 spam Field FileId(0) 17..30 21..25
598 FileId(0) 67..71 Read
604 fn test_find_all_refs_impl_item_name() {
613 f Function FileId(0) 27..43 30..31
620 fn test_find_all_refs_enum_var_name() {
630 B Variant FileId(0) 22..23 22..23
637 fn test_find_all_refs_enum_var_field() {
647 field Field FileId(0) 26..35 26..31
654 fn test_find_all_refs_two_modules() {
662 let i = foo::Foo { n: 5 };
673 let i = bar::Bar { n: 5 };
684 let i = foo::Foo$0 { n: 5 };
688 Foo Struct FileId(1) 17..51 28..31
697 fn test_find_all_refs_decl_module() {
706 let i = Foo { n: 5 };
715 foo Module FileId(0) 0..8 4..7
723 fn test_find_all_refs_decl_module_on_self() {
733 foo Module FileId(0) 0..8 4..7
741 fn test_find_all_refs_decl_module_on_self_crate_root() {
748 Module FileId(0) 0..10
755 fn test_find_all_refs_super_mod_vis() {
766 let i = Foo { n: 5 };
770 pub(super) struct Foo$0 {
775 Foo Struct FileId(2) 0..41 18..21
784 fn test_find_all_refs_with_scope() {
793 fn f() { super::quux(); }
796 fn f() { super::quux(); }
803 quux Function FileId(0) 19..35 26..30
812 Some(SearchScope::single_file(FileId(2))),
814 quux Function FileId(0) 19..35 26..30
822 fn test_find_all_refs_macro_def() {
826 macro_rules! m1$0 { () => (()) }
834 m1 Macro FileId(0) 0..46 29..31
843 fn test_basic_highlight_read_write() {
852 i Local FileId(0) 19..24 23..24 Write
854 FileId(0) 34..35 Write
855 FileId(0) 38..39 Read
861 fn test_basic_highlight_field_read_write() {
874 f Field FileId(0) 15..21 15..16
876 FileId(0) 55..56 Read
877 FileId(0) 68..69 Write
883 fn test_basic_highlight_decl_no_write() {
892 i Local FileId(0) 19..20 19..20
894 FileId(0) 26..27 Write
900 fn test_find_struct_function_refs_outside_module() {
907 pub fn new$0() -> Foo { Foo }
912 let _f = foo::Foo::new();
916 new Function FileId(0) 54..81 61..64
924 fn test_find_all_refs_nested_module() {
938 f Function FileId(0) 22..31 25..26
947 fn test_find_all_refs_struct_pat() {
961 field Field FileId(0) 15..24 15..20
963 FileId(0) 68..73 Read
969 fn test_find_all_refs_enum_var_pat() {
980 En::Variant { field } => {}
985 field Field FileId(0) 32..41 32..37
987 FileId(0) 102..107 Read
993 fn test_find_all_refs_enum_var_privacy() {
1005 m::En::Variant { field: 0 }
1009 field Field FileId(0) 56..65 56..61
1011 FileId(0) 125..130 Read
1017 fn test_find_self_refs() {
1020 struct Foo { bar: i32 }
1034 self SelfParam FileId(0) 47..51 47..51
1036 FileId(0) 71..75 Read
1037 FileId(0) 152..156 Read
1043 fn test_find_self_refs_decl() {
1046 struct Foo { bar: i32 }
1055 self SelfParam FileId(0) 47..51 47..51
1057 FileId(0) 63..67 Read
1062 fn check(ra_fixture: &str, expect: Expect) {
1063 check_with_scope(ra_fixture, None, expect)
1066 fn check_with_scope(ra_fixture: &str, search_scope: Option<SearchScope>, expect: Expect) {
1067 let (analysis, pos) = fixture::position(ra_fixture);
1068 let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
1070 let mut actual = String::new();
1071 if let Some(decl) = refs.declaration {
1072 format_to!(actual, "{}", decl.nav.debug_render());
1073 if let Some(access) = decl.access {
1074 format_to!(actual, " {:?}", access)
1079 for (file_id, references) in refs.references {
1080 for (range, access) in references {
1081 format_to!(actual, "{:?} {:?}", file_id, range);
1082 if let Some(access) = access {
1083 format_to!(actual, " {:?}", access);
1088 expect.assert_eq(actual.trim_start())
1092 fn test_find_lifetimes_function() {
1096 impl<'a> Foo<'a> for &'a () {}
1097 fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> {
1098 fn bar<'a>(_: &'a ()) {}
1103 'a LifetimeParam FileId(0) 55..57 55..57
1115 fn test_find_lifetimes_type_alias() {
1118 type Foo<'a, T> where T: 'a$0 = &'a T;
1121 'a LifetimeParam FileId(0) 9..11 9..11
1130 fn test_find_lifetimes_trait_impl() {
1136 impl<'a> Foo<'a> for &'a () {
1137 fn foo() -> &'a$0 () {
1143 'a LifetimeParam FileId(0) 47..49 47..49
1153 fn test_map_range_to_original() {
1156 macro_rules! foo {($i:ident) => {$i} }
1163 a Local FileId(0) 59..60 59..60
1165 FileId(0) 80..81 Read
1171 fn test_map_range_to_original_ref() {
1174 macro_rules! foo {($i:ident) => {$i} }
1181 a Local FileId(0) 59..60 59..60
1183 FileId(0) 80..81 Read
1189 fn test_find_labels() {
1192 fn foo<'a>() -> &'a () {
1202 'a Label FileId(0) 29..32 29..31
1211 fn test_find_const_param() {
1214 fn foo<const FOO$0: usize>() -> usize {
1219 FOO ConstParam FileId(0) 7..23 13..16
1230 trait Foo$0 where Self: {}
1235 Foo Trait FileId(0) 0..24 6..9
1243 fn test_trait_self() {
1246 trait Foo where Self$0 {
1253 Self TypeParam FileId(0) 6..9 6..9
1267 impl Foo where Self: {
1272 Foo Struct FileId(0) 0..11 7..10
1283 impl Foo where Self: {
1288 impl Impl FileId(0) 13..57 18..21
1297 fn test_self_variant_with_payload() {
1305 Self::Bar$0() => (),
1312 Bar Variant FileId(0) 11..16 11..14
1320 fn test_attr_differs_from_fn_with_same_name() {
1329 test Function FileId(0) 0..33 11..15
1337 fn test_const_in_pattern() {
1340 const A$0: i32 = 42;
1351 A Const FileId(0) 0..18 6..7
1362 fn test_primitives() {
1365 fn foo(_: bool) -> bo$0ol { true }
1375 fn test_transitive() {
1378 //- /level3.rs new_source_root:local crate:level3
1380 //- /level2.rs new_source_root:local crate:level2 deps:level3
1381 pub use level3::Foo;
1382 //- /level1.rs new_source_root:local crate:level1 deps:level2
1383 pub use level2::Foo;
1384 //- /level0.rs new_source_root:local crate:level0 deps:level1
1385 pub use level1::Foo;
1388 Foo Struct FileId(0) 0..15 11..14
1398 fn test_decl_macro_references() {
1401 //- /lib.rs crate:lib
1409 macro_rules! foo$0 {
1410 () => {struct Foo;};
1414 //- /other.rs crate:other deps:lib new_source_root:local
1418 foo Macro FileId(1) 0..61 29..32
1428 fn macro_doesnt_reference_attribute_on_call() {
1435 #[proc_macro_test::attr_noop]
1440 m Macro FileId(0) 0..32 13..14