diff options
Diffstat (limited to 'crates/ra_ssr/src/search.rs')
-rw-r--r-- | crates/ra_ssr/src/search.rs | 98 |
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 | }; |
8 | use ra_db::FileRange; | 8 | use ra_db::{FileId, FileRange}; |
9 | use ra_ide_db::{ | 9 | use ra_ide_db::{ |
10 | defs::Definition, | 10 | defs::Definition, |
11 | search::{Reference, SearchScope}, | 11 | search::{Reference, SearchScope}, |
12 | }; | 12 | }; |
13 | use ra_syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; | 13 | use ra_syntax::{ast, AstNode, SyntaxKind, SyntaxNode}; |
14 | use rustc_hash::FxHashSet; | ||
14 | use test_utils::mark; | 15 | use 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(¤t_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 | ||
202 | impl UsageCache { | 252 | impl UsageCache { |