aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ssr/src/search.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ssr/src/search.rs')
-rw-r--r--crates/ra_ssr/src/search.rs98
1 files changed, 74 insertions, 24 deletions
diff --git a/crates/ra_ssr/src/search.rs b/crates/ra_ssr/src/search.rs
index bcf0f0468..85ffa2ac2 100644
--- a/crates/ra_ssr/src/search.rs
+++ b/crates/ra_ssr/src/search.rs
@@ -5,12 +5,13 @@ use crate::{
5 resolving::{ResolvedPath, ResolvedPattern, ResolvedRule}, 5 resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
6 Match, MatchFinder, 6 Match, MatchFinder,
7}; 7};
8use ra_db::FileRange; 8use ra_db::{FileId, FileRange};
9use ra_ide_db::{ 9use ra_ide_db::{
10 defs::Definition, 10 defs::Definition,
11 search::{Reference, SearchScope}, 11 search::{Reference, SearchScope},
12}; 12};
13use ra_syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; 13use ra_syntax::{ast, AstNode, SyntaxKind, SyntaxNode};
14use rustc_hash::FxHashSet;
14use test_utils::mark; 15use test_utils::mark;
15 16
16/// A cache for the results of find_usages. This is for when we have multiple patterns that have the 17/// A cache for the results of find_usages. This is for when we have multiple patterns that have the
@@ -32,6 +33,15 @@ impl<'db> MatchFinder<'db> {
32 usage_cache: &mut UsageCache, 33 usage_cache: &mut UsageCache,
33 matches_out: &mut Vec<Match>, 34 matches_out: &mut Vec<Match>,
34 ) { 35 ) {
36 if rule.pattern.contains_self {
37 // If the pattern contains `self` we restrict the scope of the search to just the
38 // current method. No other method can reference the same `self`. This makes the
39 // behavior of `self` consistent with other variables.
40 if let Some(current_function) = self.resolution_scope.current_function() {
41 self.slow_scan_node(&current_function, rule, &None, matches_out);
42 }
43 return;
44 }
35 if pick_path_for_usages(&rule.pattern).is_none() { 45 if pick_path_for_usages(&rule.pattern).is_none() {
36 self.slow_scan(rule, matches_out); 46 self.slow_scan(rule, matches_out);
37 return; 47 return;
@@ -54,11 +64,7 @@ impl<'db> MatchFinder<'db> {
54 mark::hit!(use_declaration_with_braces); 64 mark::hit!(use_declaration_with_braces);
55 continue; 65 continue;
56 } 66 }
57 if let Ok(m) = 67 self.try_add_match(rule, &node_to_match, &None, matches_out);
58 matching::get_match(false, rule, &node_to_match, &None, &self.sema)
59 {
60 matches_out.push(m);
61 }
62 } 68 }
63 } 69 }
64 } 70 }
@@ -121,25 +127,39 @@ impl<'db> MatchFinder<'db> {
121 // FIXME: We should ideally have a test that checks that we edit local roots and not library 127 // FIXME: We should ideally have a test that checks that we edit local roots and not library
122 // roots. This probably would require some changes to fixtures, since currently everything 128 // roots. This probably would require some changes to fixtures, since currently everything
123 // seems to get put into a single source root. 129 // seems to get put into a single source root.
124 use ra_db::SourceDatabaseExt;
125 use ra_ide_db::symbol_index::SymbolsDatabase;
126 let mut files = Vec::new(); 130 let mut files = Vec::new();
127 for &root in self.sema.db.local_roots().iter() { 131 self.search_files_do(|file_id| {
128 let sr = self.sema.db.source_root(root); 132 files.push(file_id);
129 files.extend(sr.iter()); 133 });
130 }
131 SearchScope::files(&files) 134 SearchScope::files(&files)
132 } 135 }
133 136
134 fn slow_scan(&self, rule: &ResolvedRule, matches_out: &mut Vec<Match>) { 137 fn slow_scan(&self, rule: &ResolvedRule, matches_out: &mut Vec<Match>) {
135 use ra_db::SourceDatabaseExt; 138 self.search_files_do(|file_id| {
136 use ra_ide_db::symbol_index::SymbolsDatabase; 139 let file = self.sema.parse(file_id);
137 for &root in self.sema.db.local_roots().iter() { 140 let code = file.syntax();
138 let sr = self.sema.db.source_root(root); 141 self.slow_scan_node(code, rule, &None, matches_out);
139 for file_id in sr.iter() { 142 })
140 let file = self.sema.parse(file_id); 143 }
141 let code = file.syntax(); 144
142 self.slow_scan_node(code, rule, &None, matches_out); 145 fn search_files_do(&self, mut callback: impl FnMut(FileId)) {
146 if self.restrict_ranges.is_empty() {
147 // Unrestricted search.
148 use ra_db::SourceDatabaseExt;
149 use ra_ide_db::symbol_index::SymbolsDatabase;
150 for &root in self.sema.db.local_roots().iter() {
151 let sr = self.sema.db.source_root(root);
152 for file_id in sr.iter() {
153 callback(file_id);
154 }
155 }
156 } else {
157 // Search is restricted, deduplicate file IDs (generally only one).
158 let mut files = FxHashSet::default();
159 for range in &self.restrict_ranges {
160 if files.insert(range.file_id) {
161 callback(range.file_id);
162 }
143 } 163 }
144 } 164 }
145 } 165 }
@@ -154,9 +174,7 @@ impl<'db> MatchFinder<'db> {
154 if !is_search_permitted(code) { 174 if !is_search_permitted(code) {
155 return; 175 return;
156 } 176 }
157 if let Ok(m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) { 177 self.try_add_match(rule, &code, restrict_range, matches_out);
158 matches_out.push(m);
159 }
160 // If we've got a macro call, we already tried matching it pre-expansion, which is the only 178 // If we've got a macro call, we already tried matching it pre-expansion, which is the only
161 // way to match the whole macro, now try expanding it and matching the expansion. 179 // way to match the whole macro, now try expanding it and matching the expansion.
162 if let Some(macro_call) = ast::MacroCall::cast(code.clone()) { 180 if let Some(macro_call) = ast::MacroCall::cast(code.clone()) {
@@ -178,6 +196,38 @@ impl<'db> MatchFinder<'db> {
178 self.slow_scan_node(&child, rule, restrict_range, matches_out); 196 self.slow_scan_node(&child, rule, restrict_range, matches_out);
179 } 197 }
180 } 198 }
199
200 fn try_add_match(
201 &self,
202 rule: &ResolvedRule,
203 code: &SyntaxNode,
204 restrict_range: &Option<FileRange>,
205 matches_out: &mut Vec<Match>,
206 ) {
207 if !self.within_range_restrictions(code) {
208 mark::hit!(replace_nonpath_within_selection);
209 return;
210 }
211 if let Ok(m) = matching::get_match(false, rule, code, restrict_range, &self.sema) {
212 matches_out.push(m);
213 }
214 }
215
216 /// Returns whether `code` is within one of our range restrictions if we have any. No range
217 /// restrictions is considered unrestricted and always returns true.
218 fn within_range_restrictions(&self, code: &SyntaxNode) -> bool {
219 if self.restrict_ranges.is_empty() {
220 // There is no range restriction.
221 return true;
222 }
223 let node_range = self.sema.original_range(code);
224 for range in &self.restrict_ranges {
225 if range.file_id == node_range.file_id && range.range.contains_range(node_range.range) {
226 return true;
227 }
228 }
229 false
230 }
181} 231}
182 232
183/// Returns whether we support matching within `node` and all of its ancestors. 233/// Returns whether we support matching within `node` and all of its ancestors.
@@ -196,7 +246,7 @@ fn is_search_permitted(node: &SyntaxNode) -> bool {
196 // and the code is `use foo::{baz, bar}`, we'll match `bar`, since it resolves to `foo::bar`. 246 // and the code is `use foo::{baz, bar}`, we'll match `bar`, since it resolves to `foo::bar`.
197 // However we'll then replace just the part we matched `bar`. We probably need to instead remove 247 // However we'll then replace just the part we matched `bar`. We probably need to instead remove
198 // `bar` and insert a new use declaration. 248 // `bar` and insert a new use declaration.
199 node.kind() != SyntaxKind::USE_ITEM 249 node.kind() != SyntaxKind::USE
200} 250}
201 251
202impl UsageCache { 252impl UsageCache {