diff options
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | crates/ra_ide/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide/src/references.rs | 204 | ||||
-rw-r--r-- | crates/ra_ide/src/references/search_scope.rs | 145 | ||||
-rw-r--r-- | crates/ra_ide_db/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide_db/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_ide_db/src/marks.rs | 1 | ||||
-rw-r--r-- | crates/ra_ide_db/src/search.rs | 319 |
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" | |||
1060 | dependencies = [ | 1059 | dependencies = [ |
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" | |||
19 | log = "0.4.8" | 19 | log = "0.4.8" |
20 | rustc-hash = "1.1.0" | 20 | rustc-hash = "1.1.0" |
21 | rand = { version = "0.7.3", features = ["small_rng"] } | 21 | rand = { version = "0.7.3", features = ["small_rng"] } |
22 | once_cell = "1.3.1" | ||
23 | 22 | ||
24 | ra_syntax = { path = "../ra_syntax" } | 23 | ra_syntax = { path = "../ra_syntax" } |
25 | ra_text_edit = { path = "../ra_text_edit" } | 24 | ra_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 | ||
12 | mod rename; | 12 | mod rename; |
13 | mod search_scope; | ||
14 | 13 | ||
15 | use hir::Semantics; | 14 | use hir::Semantics; |
16 | use once_cell::unsync::Lazy; | ||
17 | use ra_db::SourceDatabaseExt; | ||
18 | use ra_ide_db::{ | 15 | use 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 | }; |
22 | use ra_prof::profile; | 20 | use ra_prof::profile; |
23 | use ra_syntax::{ | 21 | use 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 | }; |
28 | use test_utils::tested_by; | ||
29 | 26 | ||
30 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; | 27 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; |
31 | 28 | ||
32 | pub(crate) use self::rename::rename; | 29 | pub(crate) use self::rename::rename; |
33 | 30 | ||
34 | pub use self::search_scope::SearchScope; | 31 | pub use ra_ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; |
35 | 32 | ||
36 | #[derive(Debug, Clone)] | 33 | #[derive(Debug, Clone)] |
37 | pub struct ReferenceSearchResult { | 34 | pub 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)] | ||
50 | pub struct Reference { | ||
51 | pub file_range: FileRange, | ||
52 | pub kind: ReferenceKind, | ||
53 | pub access: Option<ReferenceAccess>, | ||
54 | } | ||
55 | |||
56 | #[derive(Debug, Clone, PartialEq)] | ||
57 | pub enum ReferenceKind { | ||
58 | StructLiteral, | ||
59 | Other, | ||
60 | } | ||
61 | |||
62 | #[derive(Debug, Copy, Clone, PartialEq)] | ||
63 | pub enum ReferenceAccess { | ||
64 | Read, | ||
65 | Write, | ||
66 | } | ||
67 | |||
68 | impl ReferenceSearchResult { | 46 | impl 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 | ||
144 | pub(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 | |||
165 | fn find_name( | 123 | fn 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 | ||
182 | fn 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 | |||
248 | fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { | 140 | fn 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 | ||
267 | fn 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 | |||
298 | fn 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 | |||
309 | fn get_struct_def_name_for_struc_litetal_search( | 159 | fn 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 | ||
327 | fn 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)] |
342 | mod tests { | 178 | mod 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. | ||
5 | use std::mem; | ||
6 | |||
7 | use hir::{DefWithBody, HasSource, ModuleSource}; | ||
8 | use ra_db::{FileId, SourceDatabaseExt}; | ||
9 | use ra_prof::profile; | ||
10 | use ra_syntax::{AstNode, TextRange}; | ||
11 | use rustc_hash::FxHashMap; | ||
12 | |||
13 | use ra_ide_db::RootDatabase; | ||
14 | |||
15 | use super::Definition; | ||
16 | |||
17 | pub struct SearchScope { | ||
18 | entries: FxHashMap<FileId, Option<TextRange>>, | ||
19 | } | ||
20 | |||
21 | impl 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 | |||
139 | impl 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" | |||
16 | fst = { version = "0.3.5", default-features = false } | 16 | fst = { version = "0.3.5", default-features = false } |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
18 | superslice = "1.0.0" | 18 | superslice = "1.0.0" |
19 | once_cell = "1.3.1" | ||
19 | 20 | ||
20 | ra_syntax = { path = "../ra_syntax" } | 21 | ra_syntax = { path = "../ra_syntax" } |
21 | ra_text_edit = { path = "../ra_text_edit" } | 22 | ra_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; | |||
9 | pub mod symbol_index; | 9 | pub mod symbol_index; |
10 | pub mod change; | 10 | pub mod change; |
11 | pub mod defs; | 11 | pub mod defs; |
12 | pub mod search; | ||
12 | pub mod imports_locator; | 13 | pub mod imports_locator; |
13 | mod wasm_shims; | 14 | mod 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 | |||
7 | use std::mem; | ||
8 | |||
9 | use hir::{DefWithBody, HasSource, ModuleSource, Semantics}; | ||
10 | use once_cell::unsync::Lazy; | ||
11 | use ra_db::{FileId, FileRange, SourceDatabaseExt}; | ||
12 | use ra_prof::profile; | ||
13 | use ra_syntax::{ | ||
14 | algo::find_node_at_offset, ast, match_ast, AstNode, TextRange, TextUnit, TokenAtOffset, | ||
15 | }; | ||
16 | use rustc_hash::FxHashMap; | ||
17 | use test_utils::tested_by; | ||
18 | |||
19 | use crate::{ | ||
20 | defs::{classify_name_ref, Definition}, | ||
21 | RootDatabase, | ||
22 | }; | ||
23 | |||
24 | #[derive(Debug, Clone)] | ||
25 | pub struct Reference { | ||
26 | pub file_range: FileRange, | ||
27 | pub kind: ReferenceKind, | ||
28 | pub access: Option<ReferenceAccess>, | ||
29 | } | ||
30 | |||
31 | #[derive(Debug, Clone, PartialEq)] | ||
32 | pub enum ReferenceKind { | ||
33 | StructLiteral, | ||
34 | Other, | ||
35 | } | ||
36 | |||
37 | #[derive(Debug, Copy, Clone, PartialEq)] | ||
38 | pub 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. | ||
47 | pub struct SearchScope { | ||
48 | entries: FxHashMap<FileId, Option<TextRange>>, | ||
49 | } | ||
50 | |||
51 | impl 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 | |||
96 | impl 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 | |||
105 | impl 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 | |||
265 | fn 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 | |||
296 | fn 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 | |||
310 | fn 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 | } | ||