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 pub(crate) mod rename;
14 use hir::{PathResolution, Semantics};
17 defs::{Definition, NameClass, NameRefClass},
18 search::{ReferenceAccess, SearchScope},
21 use rustc_hash::FxHashMap;
23 algo::find_node_at_offset,
24 ast::{self, NameOwner},
25 match_ast, AstNode, SyntaxNode, TextRange, T,
28 use crate::{display::TryToNav, FilePosition, NavigationTarget};
30 #[derive(Debug, Clone)]
31 pub struct ReferenceSearchResult {
32 pub declaration: Declaration,
33 pub references: FxHashMap<FileId, Vec<(TextRange, Option<ReferenceAccess>)>>,
36 #[derive(Debug, Clone)]
37 pub struct Declaration {
38 pub nav: NavigationTarget,
39 pub access: Option<ReferenceAccess>,
42 // Feature: Find All References
44 // Shows all references of the item at the cursor location
47 // | Editor | Shortcut
49 // | VS Code | kbd:[Shift+Alt+F12]
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 (def, is_literal_search) =
60 if let Some(name) = get_name_of_item_declaration(&syntax, position) {
61 (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true)
63 (find_def(&sema, &syntax, position)?, false)
66 let mut usages = def.usages(sema).set_scope(search_scope).all();
67 if is_literal_search {
68 // filter for constructor-literals
69 let refs = usages.references.values_mut();
71 Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => {
73 it.retain(|reference| {
77 .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref))
80 usages.references.retain(|_, it| !it.is_empty());
82 Definition::ModuleDef(hir::ModuleDef::Adt(_))
83 | Definition::ModuleDef(hir::ModuleDef::Variant(_)) => {
85 it.retain(|reference| {
86 reference.name.as_name_ref().map_or(false, is_lit_name_ref)
89 usages.references.retain(|_, it| !it.is_empty());
94 let nav = def.try_to_nav(sema.db)?;
95 let decl_range = nav.focus_or_full_range();
97 let declaration = Declaration { nav, access: decl_access(&def, &syntax, decl_range) };
98 let references = usages
100 .map(|(file_id, refs)| {
101 (file_id, refs.into_iter().map(|file_ref| (file_ref.range, file_ref.access)).collect())
105 Some(ReferenceSearchResult { declaration, references })
109 sema: &Semantics<RootDatabase>,
111 position: FilePosition,
112 ) -> Option<Definition> {
113 let def = match sema.find_node_at_offset_with_descend(syntax, position.offset)? {
114 ast::NameLike::NameRef(name_ref) => {
115 NameRefClass::classify(sema, &name_ref)?.referenced(sema.db)
117 ast::NameLike::Name(name) => {
118 NameClass::classify(sema, &name)?.referenced_or_defined(sema.db)
120 ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
121 .map(|class| class.referenced(sema.db))
123 NameClass::classify_lifetime(sema, &lifetime)
124 .map(|class| class.referenced_or_defined(sema.db))
130 fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
132 Definition::Local(_) | Definition::Field(_) => {}
136 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
137 if stmt.initializer().is_some() {
138 let pat = stmt.pat()?;
139 if let ast::Pat::IdentPat(it) = pat {
140 if it.mut_token().is_some() {
141 return Some(ReferenceAccess::Write);
149 fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> {
150 let token = syntax.token_at_offset(position.offset).right_biased()?;
151 let kind = token.kind();
153 ast::Struct::cast(token.parent())
154 .filter(|struct_| struct_.field_list().is_none())
155 .and_then(|struct_| struct_.name())
156 } else if kind == T!['{'] {
158 match (token.parent()) {
159 ast::RecordFieldList(rfl) => match_ast! {
160 match (rfl.syntax().parent()?) {
161 ast::Variant(it) => it.name(),
162 ast::Struct(it) => it.name(),
163 ast::Union(it) => it.name(),
167 ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(),
171 } else if kind == T!['('] {
172 let tfl = ast::TupleFieldList::cast(token.parent())?;
174 match (tfl.syntax().parent()?) {
175 ast::Variant(it) => it.name(),
176 ast::Struct(it) => it.name(),
185 fn is_enum_lit_name_ref(
186 sema: &Semantics<RootDatabase>,
188 name_ref: &ast::NameRef,
190 let path_is_variant_of_enum = |path: ast::Path| {
192 sema.resolve_path(&path),
193 Some(PathResolution::Def(hir::ModuleDef::Variant(variant)))
194 if variant.parent_enum(sema.db) == enum_
200 .find_map(|ancestor| {
203 ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum),
204 ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum),
212 fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool {
213 path.and_then(|path| path.segment())
214 .and_then(|segment| segment.name_ref())
215 .map_or(false, |segment| segment == *name_ref)
218 fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool {
219 name_ref.syntax().ancestors().find_map(|ancestor| {
222 ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)),
223 ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)),
232 use expect_test::{expect, Expect};
233 use ide_db::base_db::FileId;
236 use crate::{fixture, SearchScope};
239 fn test_struct_literal_after_space() {
250 f = Foo {a: Foo::f()};
254 Foo Struct FileId(0) 0..26 7..10
262 fn test_struct_literal_before_space() {
272 Foo Struct FileId(0) 0..13 7..10
281 fn test_struct_literal_with_generic_type() {
291 Foo Struct FileId(0) 0..16 7..10
299 fn test_struct_literal_for_tuple() {
310 Foo Struct FileId(0) 0..16 7..10
318 fn test_struct_literal_for_union() {
331 Foo Union FileId(0) 0..24 6..9
339 fn test_enum_after_space() {
355 Foo Enum FileId(0) 0..37 5..8
365 fn test_variant_record_after_space() {
375 f = Foo::A { n: 92 };
379 A Variant FileId(0) 15..27 15..16
386 fn test_variant_tuple_before_paren() {
400 A Variant FileId(0) 15..21 15..16
408 fn test_enum_before_space() {
421 Foo Enum FileId(0) 0..26 5..8
430 fn test_enum_with_generic_type() {
443 Foo Enum FileId(0) 0..32 5..8
451 fn test_enum_for_tuple() {
464 Foo Enum FileId(0) 0..33 5..8
472 fn test_find_all_refs_for_local() {
487 i Local FileId(0) 20..25 24..25 Write
489 FileId(0) 50..51 Write
490 FileId(0) 54..55 Read
491 FileId(0) 76..77 Write
492 FileId(0) 94..95 Write
498 fn search_filters_by_range() {
511 spam Local FileId(0) 19..23 19..23
513 FileId(0) 34..38 Read
514 FileId(0) 41..45 Read
520 fn test_find_all_refs_for_param_inside() {
523 fn foo(i : u32) -> u32 { i$0 }
526 i ValueParam FileId(0) 7..8 7..8
528 FileId(0) 25..26 Read
534 fn test_find_all_refs_for_fn_param() {
537 fn foo(i$0 : u32) -> u32 { i }
540 i ValueParam FileId(0) 7..8 7..8
542 FileId(0) 25..26 Read
548 fn test_find_all_refs_field_name() {
561 spam Field FileId(0) 17..30 21..25
563 FileId(0) 67..71 Read
569 fn test_find_all_refs_impl_item_name() {
578 f Function FileId(0) 27..43 30..31
585 fn test_find_all_refs_enum_var_name() {
595 B Variant FileId(0) 22..23 22..23
602 fn test_find_all_refs_enum_var_field() {
612 field Field FileId(0) 26..35 26..31
619 fn test_find_all_refs_two_modules() {
627 let i = foo::Foo { n: 5 };
638 let i = bar::Bar { n: 5 };
649 let i = foo::Foo$0 { n: 5 };
653 Foo Struct FileId(1) 17..51 28..31
661 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
662 // So, there are two references: the first one is a definition of the `foo` module,
663 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
665 fn test_find_all_refs_decl_module() {
674 let i = Foo { n: 5 };
683 foo Module FileId(1) 0..35
691 fn test_find_all_refs_super_mod_vis() {
702 let i = Foo { n: 5 };
706 pub(super) struct Foo$0 {
711 Foo Struct FileId(2) 0..41 18..21
720 fn test_find_all_refs_with_scope() {
729 fn f() { super::quux(); }
732 fn f() { super::quux(); }
739 quux Function FileId(0) 19..35 26..30
748 Some(SearchScope::single_file(FileId(2))),
750 quux Function FileId(0) 19..35 26..30
758 fn test_find_all_refs_macro_def() {
762 macro_rules! m1$0 { () => (()) }
770 m1 Macro FileId(0) 0..46 29..31
779 fn test_basic_highlight_read_write() {
788 i Local FileId(0) 19..24 23..24 Write
790 FileId(0) 34..35 Write
791 FileId(0) 38..39 Read
797 fn test_basic_highlight_field_read_write() {
810 f Field FileId(0) 15..21 15..16
812 FileId(0) 55..56 Read
813 FileId(0) 68..69 Write
819 fn test_basic_highlight_decl_no_write() {
828 i Local FileId(0) 19..20 19..20
830 FileId(0) 26..27 Write
836 fn test_find_struct_function_refs_outside_module() {
843 pub fn new$0() -> Foo { Foo }
848 let _f = foo::Foo::new();
852 new Function FileId(0) 54..81 61..64
860 fn test_find_all_refs_nested_module() {
874 f Function FileId(0) 22..31 25..26
883 fn test_find_all_refs_struct_pat() {
897 field Field FileId(0) 15..24 15..20
899 FileId(0) 68..73 Read
905 fn test_find_all_refs_enum_var_pat() {
916 En::Variant { field } => {}
921 field Field FileId(0) 32..41 32..37
923 FileId(0) 102..107 Read
929 fn test_find_all_refs_enum_var_privacy() {
941 m::En::Variant { field: 0 }
945 field Field FileId(0) 56..65 56..61
947 FileId(0) 125..130 Read
953 fn test_find_self_refs() {
956 struct Foo { bar: i32 }
970 self SelfParam FileId(0) 47..51 47..51
972 FileId(0) 71..75 Read
973 FileId(0) 152..156 Read
979 fn test_find_self_refs_decl() {
982 struct Foo { bar: i32 }
991 self SelfParam FileId(0) 47..51 47..51
993 FileId(0) 63..67 Read
998 fn check(ra_fixture: &str, expect: Expect) {
999 check_with_scope(ra_fixture, None, expect)
1002 fn check_with_scope(ra_fixture: &str, search_scope: Option<SearchScope>, expect: Expect) {
1003 let (analysis, pos) = fixture::position(ra_fixture);
1004 let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
1006 let mut actual = String::new();
1008 let decl = refs.declaration;
1009 format_to!(actual, "{}", decl.nav.debug_render());
1010 if let Some(access) = decl.access {
1011 format_to!(actual, " {:?}", access)
1016 for (file_id, references) in refs.references {
1017 for (range, access) in references {
1018 format_to!(actual, "{:?} {:?}", file_id, range);
1019 if let Some(access) = access {
1020 format_to!(actual, " {:?}", access);
1025 expect.assert_eq(&actual)
1029 fn test_find_lifetimes_function() {
1033 impl<'a> Foo<'a> for &'a () {}
1034 fn foo<'a, 'b: 'a>(x: &'a$0 ()) -> &'a () where &'a (): Foo<'a> {
1035 fn bar<'a>(_: &'a ()) {}
1040 'a LifetimeParam FileId(0) 55..57 55..57
1052 fn test_find_lifetimes_type_alias() {
1055 type Foo<'a, T> where T: 'a$0 = &'a T;
1058 'a LifetimeParam FileId(0) 9..11 9..11
1067 fn test_find_lifetimes_trait_impl() {
1073 impl<'a> Foo<'a> for &'a () {
1074 fn foo() -> &'a$0 () {
1080 'a LifetimeParam FileId(0) 47..49 47..49
1090 fn test_map_range_to_original() {
1093 macro_rules! foo {($i:ident) => {$i} }
1100 a Local FileId(0) 59..60 59..60
1102 FileId(0) 80..81 Read
1108 fn test_map_range_to_original_ref() {
1111 macro_rules! foo {($i:ident) => {$i} }
1118 a Local FileId(0) 59..60 59..60
1120 FileId(0) 80..81 Read
1126 fn test_find_labels() {
1129 fn foo<'a>() -> &'a () {
1139 'a Label FileId(0) 29..32 29..31
1148 fn test_find_const_param() {
1151 fn foo<const FOO$0: usize>() -> usize {
1156 FOO ConstParam FileId(0) 7..23 13..16
1164 fn test_find_self_ty_in_trait_def() {
1172 Self TypeParam FileId(0) 6..9 6..9
1180 fn test_self_variant_with_payload() {
1188 Self::Bar$0() => (),
1195 Bar Variant FileId(0) 11..16 11..14
1203 fn test_attr_differs_from_fn_with_same_name() {
1212 test Function FileId(0) 0..33 11..15
1220 fn test_attr_matches_proc_macro_fn() {
1223 #[proc_macro_attribute]
1224 fn my_proc_macro() {}
1230 my_proc_macro Function FileId(0) 0..45 27..40
1238 fn test_const_in_pattern() {
1241 const A$0: i32 = 42;
1252 A Const FileId(0) 0..18 6..7