aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-03-04 11:49:24 +0000
committerGitHub <[email protected]>2020-03-04 11:49:24 +0000
commit02b02061b6cb121204a9bd931e7cd541dba2898f (patch)
treea8e5e91aeef1d910a77ed2b664cd28e6d033de7a
parent66ec6bdfb0fbb8e99b58f3c184ef5012354e6d92 (diff)
parent4f50a3718762c9272b1929162ce62415a75eec8f (diff)
Merge #3440
3440: Move search to ra_ide_db r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--Cargo.lock2
-rw-r--r--crates/ra_ide/Cargo.toml1
-rw-r--r--crates/ra_ide/src/lib.rs5
-rw-r--r--crates/ra_ide/src/references.rs204
-rw-r--r--crates/ra_ide/src/references/search_scope.rs145
-rw-r--r--crates/ra_ide_db/Cargo.toml1
-rw-r--r--crates/ra_ide_db/src/lib.rs1
-rw-r--r--crates/ra_ide_db/src/marks.rs1
-rw-r--r--crates/ra_ide_db/src/search.rs319
9 files changed, 344 insertions, 335 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 87c288346..316dae053 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1039,7 +1039,6 @@ dependencies = [
1039 "itertools", 1039 "itertools",
1040 "join_to_string", 1040 "join_to_string",
1041 "log", 1041 "log",
1042 "once_cell",
1043 "ra_assists", 1042 "ra_assists",
1044 "ra_cfg", 1043 "ra_cfg",
1045 "ra_db", 1044 "ra_db",
@@ -1060,6 +1059,7 @@ version = "0.1.0"
1060dependencies = [ 1059dependencies = [
1061 "fst", 1060 "fst",
1062 "log", 1061 "log",
1062 "once_cell",
1063 "ra_db", 1063 "ra_db",
1064 "ra_hir", 1064 "ra_hir",
1065 "ra_prof", 1065 "ra_prof",
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 7625fc8c8..410d8de62 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -19,7 +19,6 @@ join_to_string = "0.1.3"
19log = "0.4.8" 19log = "0.4.8"
20rustc-hash = "1.1.0" 20rustc-hash = "1.1.0"
21rand = { version = "0.7.3", features = ["small_rng"] } 21rand = { version = "0.7.3", features = ["small_rng"] }
22once_cell = "1.3.1"
23 22
24ra_syntax = { path = "../ra_syntax" } 23ra_syntax = { path = "../ra_syntax" }
25ra_text_edit = { path = "../ra_text_edit" } 24ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index f61028f78..5a41f702e 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -68,9 +68,7 @@ pub use crate::{
68 folding_ranges::{Fold, FoldKind}, 68 folding_ranges::{Fold, FoldKind},
69 hover::HoverResult, 69 hover::HoverResult,
70 inlay_hints::{InlayHint, InlayKind}, 70 inlay_hints::{InlayHint, InlayKind},
71 references::{ 71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
72 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope,
73 },
74 runnables::{Runnable, RunnableKind, TestId}, 72 runnables::{Runnable, RunnableKind, TestId},
75 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 73 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
76 ssr::SsrError, 74 ssr::SsrError,
@@ -88,6 +86,7 @@ pub use ra_ide_db::{
88 feature_flags::FeatureFlags, 86 feature_flags::FeatureFlags,
89 line_index::{LineCol, LineIndex}, 87 line_index::{LineCol, LineIndex},
90 line_index_utils::translate_offset_with_edit, 88 line_index_utils::translate_offset_with_edit,
89 search::SearchScope,
91 symbol_index::Query, 90 symbol_index::Query,
92 RootDatabase, 91 RootDatabase,
93}; 92};
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index 95468d434..6e2bf4ded 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -10,28 +10,25 @@
10//! resolved to the search element definition, we get a reference. 10//! resolved to the search element definition, we get a reference.
11 11
12mod rename; 12mod rename;
13mod search_scope;
14 13
15use hir::Semantics; 14use hir::Semantics;
16use once_cell::unsync::Lazy;
17use ra_db::SourceDatabaseExt;
18use ra_ide_db::{ 15use ra_ide_db::{
19 defs::{classify_name, classify_name_ref, Definition}, 16 defs::{classify_name, classify_name_ref, Definition},
17 search::SearchScope,
20 RootDatabase, 18 RootDatabase,
21}; 19};
22use ra_prof::profile; 20use ra_prof::profile;
23use ra_syntax::{ 21use ra_syntax::{
24 algo::find_node_at_offset, 22 algo::find_node_at_offset,
25 ast::{self, NameOwner}, 23 ast::{self, NameOwner},
26 match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset, 24 AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
27}; 25};
28use test_utils::tested_by;
29 26
30use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
31 28
32pub(crate) use self::rename::rename; 29pub(crate) use self::rename::rename;
33 30
34pub use self::search_scope::SearchScope; 31pub use ra_ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
35 32
36#[derive(Debug, Clone)] 33#[derive(Debug, Clone)]
37pub struct ReferenceSearchResult { 34pub struct ReferenceSearchResult {
@@ -46,25 +43,6 @@ pub struct Declaration {
46 pub access: Option<ReferenceAccess>, 43 pub access: Option<ReferenceAccess>,
47} 44}
48 45
49#[derive(Debug, Clone)]
50pub struct Reference {
51 pub file_range: FileRange,
52 pub kind: ReferenceKind,
53 pub access: Option<ReferenceAccess>,
54}
55
56#[derive(Debug, Clone, PartialEq)]
57pub enum ReferenceKind {
58 StructLiteral,
59 Other,
60}
61
62#[derive(Debug, Copy, Clone, PartialEq)]
63pub enum ReferenceAccess {
64 Read,
65 Write,
66}
67
68impl ReferenceSearchResult { 46impl ReferenceSearchResult {
69 pub fn declaration(&self) -> &Declaration { 47 pub fn declaration(&self) -> &Declaration {
70 &self.declaration 48 &self.declaration
@@ -125,7 +103,8 @@ pub(crate) fn find_all_refs(
125 103
126 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; 104 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
127 105
128 let references = find_refs_to_def(db, &def, search_scope) 106 let references = def
107 .find_usages(db, search_scope)
129 .into_iter() 108 .into_iter()
130 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) 109 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
131 .collect(); 110 .collect();
@@ -141,27 +120,6 @@ pub(crate) fn find_all_refs(
141 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) 120 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
142} 121}
143 122
144pub(crate) fn find_refs_to_def(
145 db: &RootDatabase,
146 def: &Definition,
147 search_scope: Option<SearchScope>,
148) -> Vec<Reference> {
149 let search_scope = {
150 let base = SearchScope::for_def(&def, db);
151 match search_scope {
152 None => base,
153 Some(scope) => base.intersection(&scope),
154 }
155 };
156
157 let name = match def.name(db) {
158 None => return Vec::new(),
159 Some(it) => it.to_string(),
160 };
161
162 process_definition(db, def, name, search_scope)
163}
164
165fn find_name( 123fn find_name(
166 sema: &Semantics<RootDatabase>, 124 sema: &Semantics<RootDatabase>,
167 syntax: &SyntaxNode, 125 syntax: &SyntaxNode,
@@ -179,72 +137,6 @@ fn find_name(
179 Some(RangeInfo::new(range, def)) 137 Some(RangeInfo::new(range, def))
180} 138}
181 139
182fn process_definition(
183 db: &RootDatabase,
184 def: &Definition,
185 name: String,
186 scope: SearchScope,
187) -> Vec<Reference> {
188 let _p = profile("process_definition");
189
190 let pat = name.as_str();
191 let mut refs = vec![];
192
193 for (file_id, search_range) in scope {
194 let text = db.file_text(file_id);
195 let search_range =
196 search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text)));
197
198 let sema = Semantics::new(db);
199 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
200
201 for (idx, _) in text.match_indices(pat) {
202 let offset = TextUnit::from_usize(idx);
203 if !search_range.contains_inclusive(offset) {
204 tested_by!(search_filters_by_range);
205 continue;
206 }
207
208 let name_ref =
209 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&tree, offset) {
210 name_ref
211 } else {
212 // Handle macro token cases
213 let token = match tree.token_at_offset(offset) {
214 TokenAtOffset::None => continue,
215 TokenAtOffset::Single(t) => t,
216 TokenAtOffset::Between(_, t) => t,
217 };
218 let expanded = sema.descend_into_macros(token);
219 match ast::NameRef::cast(expanded.parent()) {
220 Some(name_ref) => name_ref,
221 _ => continue,
222 }
223 };
224
225 if let Some(d) = classify_name_ref(&sema, &name_ref) {
226 let d = d.definition();
227 if &d == def {
228 let kind =
229 if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) {
230 ReferenceKind::StructLiteral
231 } else {
232 ReferenceKind::Other
233 };
234
235 let file_range = sema.original_range(name_ref.syntax());
236 refs.push(Reference {
237 file_range,
238 kind,
239 access: reference_access(&d, &name_ref),
240 });
241 }
242 }
243 }
244 }
245 refs
246}
247
248fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { 140fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
249 match def { 141 match def {
250 Definition::Local(_) | Definition::StructField(_) => {} 142 Definition::Local(_) | Definition::StructField(_) => {}
@@ -264,48 +156,6 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio
264 None 156 None
265} 157}
266 158
267fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
268 // Only Locals and Fields have accesses for now.
269 match def {
270 Definition::Local(_) | Definition::StructField(_) => {}
271 _ => return None,
272 };
273
274 let mode = name_ref.syntax().ancestors().find_map(|node| {
275 match_ast! {
276 match (node) {
277 ast::BinExpr(expr) => {
278 if expr.op_kind()?.is_assignment() {
279 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
280 // FIXME: This is not terribly accurate.
281 if let Some(lhs) = expr.lhs() {
282 if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() {
283 return Some(ReferenceAccess::Write);
284 }
285 }
286 }
287 Some(ReferenceAccess::Read)
288 },
289 _ => {None}
290 }
291 }
292 });
293
294 // Default Locals and Fields to read
295 mode.or(Some(ReferenceAccess::Read))
296}
297
298fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
299 name_ref
300 .syntax()
301 .ancestors()
302 .find_map(ast::RecordLit::cast)
303 .and_then(|l| l.path())
304 .and_then(|p| p.segment())
305 .map(|p| p.name_ref().as_ref() == Some(name_ref))
306 .unwrap_or(false)
307}
308
309fn get_struct_def_name_for_struc_litetal_search( 159fn get_struct_def_name_for_struc_litetal_search(
310 syntax: &SyntaxNode, 160 syntax: &SyntaxNode,
311 position: FilePosition, 161 position: FilePosition,
@@ -324,20 +174,6 @@ fn get_struct_def_name_for_struc_litetal_search(
324 None 174 None
325} 175}
326 176
327fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool {
328 name_ref
329 .syntax()
330 .ancestors()
331 .find_map(ast::CallExpr::cast)
332 .and_then(|c| match c.expr()? {
333 ast::Expr::PathExpr(p) => {
334 Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref))
335 }
336 _ => None,
337 })
338 .unwrap_or(false)
339}
340
341#[cfg(test)] 177#[cfg(test)]
342mod tests { 178mod tests {
343 use test_utils::covers; 179 use test_utils::covers;
@@ -451,7 +287,7 @@ mod tests {
451 287
452 #[test] 288 #[test]
453 fn search_filters_by_range() { 289 fn search_filters_by_range() {
454 covers!(search_filters_by_range); 290 covers!(ra_ide_db::search_filters_by_range);
455 let code = r#" 291 let code = r#"
456 fn foo() { 292 fn foo() {
457 let spam<|> = 92; 293 let spam<|> = 92;
@@ -767,7 +603,10 @@ mod tests {
767 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) { 603 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
768 res.declaration().assert_match(expected_decl); 604 res.declaration().assert_match(expected_decl);
769 assert_eq!(res.references.len(), expected_refs.len()); 605 assert_eq!(res.references.len(), expected_refs.len());
770 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i])); 606 res.references()
607 .iter()
608 .enumerate()
609 .for_each(|(i, r)| ref_assert_match(r, expected_refs[i]));
771 } 610 }
772 611
773 impl Declaration { 612 impl Declaration {
@@ -785,21 +624,16 @@ mod tests {
785 } 624 }
786 } 625 }
787 626
788 impl Reference { 627 fn ref_debug_render(r: &Reference) -> String {
789 fn debug_render(&self) -> String { 628 let mut s = format!("{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
790 let mut s = format!( 629 if let Some(access) = r.access {
791 "{:?} {:?} {:?}", 630 s.push_str(&format!(" {:?}", access));
792 self.file_range.file_id, self.file_range.range, self.kind
793 );
794 if let Some(access) = self.access {
795 s.push_str(&format!(" {:?}", access));
796 }
797 s
798 } 631 }
632 s
633 }
799 634
800 fn assert_match(&self, expected: &str) { 635 fn ref_assert_match(r: &Reference, expected: &str) {
801 let actual = self.debug_render(); 636 let actual = ref_debug_render(r);
802 test_utils::assert_eq_text!(expected.trim(), actual.trim(),); 637 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
803 }
804 } 638 }
805} 639}
diff --git a/crates/ra_ide/src/references/search_scope.rs b/crates/ra_ide/src/references/search_scope.rs
deleted file mode 100644
index d98c84d91..000000000
--- a/crates/ra_ide/src/references/search_scope.rs
+++ /dev/null
@@ -1,145 +0,0 @@
1//! Generally, `search_scope` returns files that might contain references for the element.
2//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
3//! In some cases, the location of the references is known to within a `TextRange`,
4//! e.g. for things like local variables.
5use std::mem;
6
7use hir::{DefWithBody, HasSource, ModuleSource};
8use ra_db::{FileId, SourceDatabaseExt};
9use ra_prof::profile;
10use ra_syntax::{AstNode, TextRange};
11use rustc_hash::FxHashMap;
12
13use ra_ide_db::RootDatabase;
14
15use super::Definition;
16
17pub struct SearchScope {
18 entries: FxHashMap<FileId, Option<TextRange>>,
19}
20
21impl SearchScope {
22 fn empty() -> SearchScope {
23 SearchScope { entries: FxHashMap::default() }
24 }
25
26 pub(crate) fn for_def(def: &Definition, db: &RootDatabase) -> SearchScope {
27 let _p = profile("search_scope");
28 let module = match def.module(db) {
29 Some(it) => it,
30 None => return SearchScope::empty(),
31 };
32 let module_src = module.definition_source(db);
33 let file_id = module_src.file_id.original_file(db);
34
35 if let Definition::Local(var) = def {
36 let range = match var.parent(db) {
37 DefWithBody::Function(f) => f.source(db).value.syntax().text_range(),
38 DefWithBody::Const(c) => c.source(db).value.syntax().text_range(),
39 DefWithBody::Static(s) => s.source(db).value.syntax().text_range(),
40 };
41 let mut res = FxHashMap::default();
42 res.insert(file_id, Some(range));
43 return SearchScope::new(res);
44 }
45
46 let vis = def.visibility(db).as_ref().map(|v| v.syntax().to_string()).unwrap_or_default();
47
48 if vis.as_str() == "pub(super)" {
49 if let Some(parent_module) = module.parent(db) {
50 let mut res = FxHashMap::default();
51 let parent_src = parent_module.definition_source(db);
52 let file_id = parent_src.file_id.original_file(db);
53
54 match parent_src.value {
55 ModuleSource::Module(m) => {
56 let range = Some(m.syntax().text_range());
57 res.insert(file_id, range);
58 }
59 ModuleSource::SourceFile(_) => {
60 res.insert(file_id, None);
61 res.extend(parent_module.children(db).map(|m| {
62 let src = m.definition_source(db);
63 (src.file_id.original_file(db), None)
64 }));
65 }
66 }
67 return SearchScope::new(res);
68 }
69 }
70
71 if vis.as_str() != "" {
72 let source_root_id = db.file_source_root(file_id);
73 let source_root = db.source_root(source_root_id);
74 let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
75
76 // FIXME: add "pub(in path)"
77
78 if vis.as_str() == "pub(crate)" {
79 return SearchScope::new(res);
80 }
81 if vis.as_str() == "pub" {
82 let krate = module.krate();
83 for rev_dep in krate.reverse_dependencies(db) {
84 let root_file = rev_dep.root_file(db);
85 let source_root_id = db.file_source_root(root_file);
86 let source_root = db.source_root(source_root_id);
87 res.extend(source_root.walk().map(|id| (id, None)));
88 }
89 return SearchScope::new(res);
90 }
91 }
92
93 let mut res = FxHashMap::default();
94 let range = match module_src.value {
95 ModuleSource::Module(m) => Some(m.syntax().text_range()),
96 ModuleSource::SourceFile(_) => None,
97 };
98 res.insert(file_id, range);
99 SearchScope::new(res)
100 }
101
102 fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope {
103 SearchScope { entries }
104 }
105 pub fn single_file(file: FileId) -> SearchScope {
106 SearchScope::new(std::iter::once((file, None)).collect())
107 }
108 pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope {
109 let (mut small, mut large) = (&self.entries, &other.entries);
110 if small.len() > large.len() {
111 mem::swap(&mut small, &mut large)
112 }
113
114 let res = small
115 .iter()
116 .filter_map(|(file_id, r1)| {
117 let r2 = large.get(file_id)?;
118 let r = intersect_ranges(*r1, *r2)?;
119 Some((*file_id, r))
120 })
121 .collect();
122 return SearchScope::new(res);
123
124 fn intersect_ranges(
125 r1: Option<TextRange>,
126 r2: Option<TextRange>,
127 ) -> Option<Option<TextRange>> {
128 match (r1, r2) {
129 (None, r) | (r, None) => Some(r),
130 (Some(r1), Some(r2)) => {
131 let r = r1.intersection(&r2)?;
132 Some(Some(r))
133 }
134 }
135 }
136 }
137}
138
139impl IntoIterator for SearchScope {
140 type Item = (FileId, Option<TextRange>);
141 type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
142 fn into_iter(self) -> Self::IntoIter {
143 self.entries.into_iter()
144 }
145}
diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml
index 7ff1a536e..52f0f23df 100644
--- a/crates/ra_ide_db/Cargo.toml
+++ b/crates/ra_ide_db/Cargo.toml
@@ -16,6 +16,7 @@ rayon = "1.3.0"
16fst = { version = "0.3.5", default-features = false } 16fst = { version = "0.3.5", default-features = false }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18superslice = "1.0.0" 18superslice = "1.0.0"
19once_cell = "1.3.1"
19 20
20ra_syntax = { path = "../ra_syntax" } 21ra_syntax = { path = "../ra_syntax" }
21ra_text_edit = { path = "../ra_text_edit" } 22ra_text_edit = { path = "../ra_text_edit" }
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index aa312c140..79f48c9e3 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -9,6 +9,7 @@ pub mod feature_flags;
9pub mod symbol_index; 9pub mod symbol_index;
10pub mod change; 10pub mod change;
11pub mod defs; 11pub mod defs;
12pub mod search;
12pub mod imports_locator; 13pub mod imports_locator;
13mod wasm_shims; 14mod wasm_shims;
14 15
diff --git a/crates/ra_ide_db/src/marks.rs b/crates/ra_ide_db/src/marks.rs
index d088fa257..4f0a22af0 100644
--- a/crates/ra_ide_db/src/marks.rs
+++ b/crates/ra_ide_db/src/marks.rs
@@ -6,4 +6,5 @@ test_utils::marks![
6 goto_def_for_fields 6 goto_def_for_fields
7 goto_def_for_record_fields 7 goto_def_for_record_fields
8 goto_def_for_field_init_shorthand 8 goto_def_for_field_init_shorthand
9 search_filters_by_range
9]; 10];
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs
new file mode 100644
index 000000000..6f198df04
--- /dev/null
+++ b/crates/ra_ide_db/src/search.rs
@@ -0,0 +1,319 @@
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
7use std::mem;
8
9use hir::{DefWithBody, HasSource, ModuleSource, Semantics};
10use once_cell::unsync::Lazy;
11use ra_db::{FileId, FileRange, SourceDatabaseExt};
12use ra_prof::profile;
13use ra_syntax::{
14 algo::find_node_at_offset, ast, match_ast, AstNode, TextRange, TextUnit, TokenAtOffset,
15};
16use rustc_hash::FxHashMap;
17use test_utils::tested_by;
18
19use crate::{
20 defs::{classify_name_ref, Definition},
21 RootDatabase,
22};
23
24#[derive(Debug, Clone)]
25pub struct Reference {
26 pub file_range: FileRange,
27 pub kind: ReferenceKind,
28 pub access: Option<ReferenceAccess>,
29}
30
31#[derive(Debug, Clone, PartialEq)]
32pub enum ReferenceKind {
33 StructLiteral,
34 Other,
35}
36
37#[derive(Debug, Copy, Clone, PartialEq)]
38pub enum ReferenceAccess {
39 Read,
40 Write,
41}
42
43/// Generally, `search_scope` returns files that might contain references for the element.
44/// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates.
45/// In some cases, the location of the references is known to within a `TextRange`,
46/// e.g. for things like local variables.
47pub struct SearchScope {
48 entries: FxHashMap<FileId, Option<TextRange>>,
49}
50
51impl SearchScope {
52 fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope {
53 SearchScope { entries }
54 }
55
56 pub fn empty() -> SearchScope {
57 SearchScope::new(FxHashMap::default())
58 }
59
60 pub fn single_file(file: FileId) -> SearchScope {
61 SearchScope::new(std::iter::once((file, None)).collect())
62 }
63
64 pub fn intersection(&self, other: &SearchScope) -> SearchScope {
65 let (mut small, mut large) = (&self.entries, &other.entries);
66 if small.len() > large.len() {
67 mem::swap(&mut small, &mut large)
68 }
69
70 let res = small
71 .iter()
72 .filter_map(|(file_id, r1)| {
73 let r2 = large.get(file_id)?;
74 let r = intersect_ranges(*r1, *r2)?;
75 Some((*file_id, r))
76 })
77 .collect();
78
79 return SearchScope::new(res);
80
81 fn intersect_ranges(
82 r1: Option<TextRange>,
83 r2: Option<TextRange>,
84 ) -> Option<Option<TextRange>> {
85 match (r1, r2) {
86 (None, r) | (r, None) => Some(r),
87 (Some(r1), Some(r2)) => {
88 let r = r1.intersection(&r2)?;
89 Some(Some(r))
90 }
91 }
92 }
93 }
94}
95
96impl IntoIterator for SearchScope {
97 type Item = (FileId, Option<TextRange>);
98 type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>;
99
100 fn into_iter(self) -> Self::IntoIter {
101 self.entries.into_iter()
102 }
103}
104
105impl Definition {
106 fn search_scope(&self, db: &RootDatabase) -> SearchScope {
107 let _p = profile("search_scope");
108 let module = match self.module(db) {
109 Some(it) => it,
110 None => return SearchScope::empty(),
111 };
112 let module_src = module.definition_source(db);
113 let file_id = module_src.file_id.original_file(db);
114
115 if let Definition::Local(var) = self {
116 let range = match var.parent(db) {
117 DefWithBody::Function(f) => f.source(db).value.syntax().text_range(),
118 DefWithBody::Const(c) => c.source(db).value.syntax().text_range(),
119 DefWithBody::Static(s) => s.source(db).value.syntax().text_range(),
120 };
121 let mut res = FxHashMap::default();
122 res.insert(file_id, Some(range));
123 return SearchScope::new(res);
124 }
125
126 let vis = self.visibility(db).as_ref().map(|v| v.syntax().to_string()).unwrap_or_default();
127
128 if vis.as_str() == "pub(super)" {
129 if let Some(parent_module) = module.parent(db) {
130 let mut res = FxHashMap::default();
131 let parent_src = parent_module.definition_source(db);
132 let file_id = parent_src.file_id.original_file(db);
133
134 match parent_src.value {
135 ModuleSource::Module(m) => {
136 let range = Some(m.syntax().text_range());
137 res.insert(file_id, range);
138 }
139 ModuleSource::SourceFile(_) => {
140 res.insert(file_id, None);
141 res.extend(parent_module.children(db).map(|m| {
142 let src = m.definition_source(db);
143 (src.file_id.original_file(db), None)
144 }));
145 }
146 }
147 return SearchScope::new(res);
148 }
149 }
150
151 if vis.as_str() != "" {
152 let source_root_id = db.file_source_root(file_id);
153 let source_root = db.source_root(source_root_id);
154 let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
155
156 // FIXME: add "pub(in path)"
157
158 if vis.as_str() == "pub(crate)" {
159 return SearchScope::new(res);
160 }
161 if vis.as_str() == "pub" {
162 let krate = module.krate();
163 for rev_dep in krate.reverse_dependencies(db) {
164 let root_file = rev_dep.root_file(db);
165 let source_root_id = db.file_source_root(root_file);
166 let source_root = db.source_root(source_root_id);
167 res.extend(source_root.walk().map(|id| (id, None)));
168 }
169 return SearchScope::new(res);
170 }
171 }
172
173 let mut res = FxHashMap::default();
174 let range = match module_src.value {
175 ModuleSource::Module(m) => Some(m.syntax().text_range()),
176 ModuleSource::SourceFile(_) => None,
177 };
178 res.insert(file_id, range);
179 SearchScope::new(res)
180 }
181
182 pub fn find_usages(
183 &self,
184 db: &RootDatabase,
185 search_scope: Option<SearchScope>,
186 ) -> Vec<Reference> {
187 let _p = profile("Definition::find_usages");
188
189 let search_scope = {
190 let base = self.search_scope(db);
191 match search_scope {
192 None => base,
193 Some(scope) => base.intersection(&scope),
194 }
195 };
196
197 let name = match self.name(db) {
198 None => return Vec::new(),
199 Some(it) => it.to_string(),
200 };
201
202 let pat = name.as_str();
203 let mut refs = vec![];
204
205 for (file_id, search_range) in search_scope {
206 let text = db.file_text(file_id);
207 let search_range =
208 search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text)));
209
210 let sema = Semantics::new(db);
211 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
212
213 for (idx, _) in text.match_indices(pat) {
214 let offset = TextUnit::from_usize(idx);
215 if !search_range.contains_inclusive(offset) {
216 tested_by!(search_filters_by_range; force);
217 continue;
218 }
219
220 let name_ref =
221 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&tree, offset) {
222 name_ref
223 } else {
224 // Handle macro token cases
225 let token = match tree.token_at_offset(offset) {
226 TokenAtOffset::None => continue,
227 TokenAtOffset::Single(t) => t,
228 TokenAtOffset::Between(_, t) => t,
229 };
230 let expanded = sema.descend_into_macros(token);
231 match ast::NameRef::cast(expanded.parent()) {
232 Some(name_ref) => name_ref,
233 _ => continue,
234 }
235 };
236
237 // FIXME: reuse sb
238 // See https://github.com/rust-lang/rust/pull/68198#issuecomment-574269098
239
240 if let Some(d) = classify_name_ref(&sema, &name_ref) {
241 let d = d.definition();
242 if &d == self {
243 let kind = if is_record_lit_name_ref(&name_ref)
244 || is_call_expr_name_ref(&name_ref)
245 {
246 ReferenceKind::StructLiteral
247 } else {
248 ReferenceKind::Other
249 };
250
251 let file_range = sema.original_range(name_ref.syntax());
252 refs.push(Reference {
253 file_range,
254 kind,
255 access: reference_access(&d, &name_ref),
256 });
257 }
258 }
259 }
260 }
261 refs
262 }
263}
264
265fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
266 // Only Locals and Fields have accesses for now.
267 match def {
268 Definition::Local(_) | Definition::StructField(_) => {}
269 _ => return None,
270 };
271
272 let mode = name_ref.syntax().ancestors().find_map(|node| {
273 match_ast! {
274 match (node) {
275 ast::BinExpr(expr) => {
276 if expr.op_kind()?.is_assignment() {
277 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
278 // FIXME: This is not terribly accurate.
279 if let Some(lhs) = expr.lhs() {
280 if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() {
281 return Some(ReferenceAccess::Write);
282 }
283 }
284 }
285 Some(ReferenceAccess::Read)
286 },
287 _ => {None}
288 }
289 }
290 });
291
292 // Default Locals and Fields to read
293 mode.or(Some(ReferenceAccess::Read))
294}
295
296fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool {
297 name_ref
298 .syntax()
299 .ancestors()
300 .find_map(ast::CallExpr::cast)
301 .and_then(|c| match c.expr()? {
302 ast::Expr::PathExpr(p) => {
303 Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref))
304 }
305 _ => None,
306 })
307 .unwrap_or(false)
308}
309
310fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
311 name_ref
312 .syntax()
313 .ancestors()
314 .find_map(ast::RecordLit::cast)
315 .and_then(|l| l.path())
316 .and_then(|p| p.segment())
317 .map(|p| p.name_ref().as_ref() == Some(name_ref))
318 .unwrap_or(false)
319}