diff options
Diffstat (limited to 'crates/ra_ide_db/src/search.rs')
-rw-r--r-- | crates/ra_ide_db/src/search.rs | 323 |
1 files changed, 0 insertions, 323 deletions
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs deleted file mode 100644 index 0b862b449..000000000 --- a/crates/ra_ide_db/src/search.rs +++ /dev/null | |||
@@ -1,323 +0,0 @@ | |||
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::{convert::TryInto, mem}; | ||
8 | |||
9 | use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility}; | ||
10 | use once_cell::unsync::Lazy; | ||
11 | use ra_db::{FileId, FileRange, SourceDatabaseExt}; | ||
12 | use ra_prof::profile; | ||
13 | use ra_syntax::{ast, match_ast, AstNode, TextRange, TextSize}; | ||
14 | use rustc_hash::FxHashMap; | ||
15 | |||
16 | use crate::{ | ||
17 | defs::{classify_name_ref, Definition, NameRefClass}, | ||
18 | RootDatabase, | ||
19 | }; | ||
20 | |||
21 | #[derive(Debug, Clone)] | ||
22 | pub struct Reference { | ||
23 | pub file_range: FileRange, | ||
24 | pub kind: ReferenceKind, | ||
25 | pub access: Option<ReferenceAccess>, | ||
26 | } | ||
27 | |||
28 | #[derive(Debug, Clone, PartialEq)] | ||
29 | pub enum ReferenceKind { | ||
30 | FieldShorthandForField, | ||
31 | FieldShorthandForLocal, | ||
32 | StructLiteral, | ||
33 | Other, | ||
34 | } | ||
35 | |||
36 | #[derive(Debug, Copy, Clone, PartialEq)] | ||
37 | pub enum ReferenceAccess { | ||
38 | Read, | ||
39 | Write, | ||
40 | } | ||
41 | |||
42 | /// Generally, `search_scope` returns files that might contain references for the element. | ||
43 | /// For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. | ||
44 | /// In some cases, the location of the references is known to within a `TextRange`, | ||
45 | /// e.g. for things like local variables. | ||
46 | pub struct SearchScope { | ||
47 | entries: FxHashMap<FileId, Option<TextRange>>, | ||
48 | } | ||
49 | |||
50 | impl SearchScope { | ||
51 | fn new(entries: FxHashMap<FileId, Option<TextRange>>) -> SearchScope { | ||
52 | SearchScope { entries } | ||
53 | } | ||
54 | |||
55 | pub fn empty() -> SearchScope { | ||
56 | SearchScope::new(FxHashMap::default()) | ||
57 | } | ||
58 | |||
59 | pub fn single_file(file: FileId) -> SearchScope { | ||
60 | SearchScope::new(std::iter::once((file, None)).collect()) | ||
61 | } | ||
62 | |||
63 | pub fn files(files: &[FileId]) -> SearchScope { | ||
64 | SearchScope::new(files.iter().map(|f| (*f, None)).collect()) | ||
65 | } | ||
66 | |||
67 | pub fn intersection(&self, other: &SearchScope) -> SearchScope { | ||
68 | let (mut small, mut large) = (&self.entries, &other.entries); | ||
69 | if small.len() > large.len() { | ||
70 | mem::swap(&mut small, &mut large) | ||
71 | } | ||
72 | |||
73 | let res = small | ||
74 | .iter() | ||
75 | .filter_map(|(file_id, r1)| { | ||
76 | let r2 = large.get(file_id)?; | ||
77 | let r = intersect_ranges(*r1, *r2)?; | ||
78 | Some((*file_id, r)) | ||
79 | }) | ||
80 | .collect(); | ||
81 | |||
82 | return SearchScope::new(res); | ||
83 | |||
84 | fn intersect_ranges( | ||
85 | r1: Option<TextRange>, | ||
86 | r2: Option<TextRange>, | ||
87 | ) -> Option<Option<TextRange>> { | ||
88 | match (r1, r2) { | ||
89 | (None, r) | (r, None) => Some(r), | ||
90 | (Some(r1), Some(r2)) => { | ||
91 | let r = r1.intersect(r2)?; | ||
92 | Some(Some(r)) | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | impl IntoIterator for SearchScope { | ||
100 | type Item = (FileId, Option<TextRange>); | ||
101 | type IntoIter = std::collections::hash_map::IntoIter<FileId, Option<TextRange>>; | ||
102 | |||
103 | fn into_iter(self) -> Self::IntoIter { | ||
104 | self.entries.into_iter() | ||
105 | } | ||
106 | } | ||
107 | |||
108 | impl Definition { | ||
109 | fn search_scope(&self, db: &RootDatabase) -> SearchScope { | ||
110 | let _p = profile("search_scope"); | ||
111 | let module = match self.module(db) { | ||
112 | Some(it) => it, | ||
113 | None => return SearchScope::empty(), | ||
114 | }; | ||
115 | let module_src = module.definition_source(db); | ||
116 | let file_id = module_src.file_id.original_file(db); | ||
117 | |||
118 | if let Definition::Local(var) = self { | ||
119 | let range = match var.parent(db) { | ||
120 | DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), | ||
121 | DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), | ||
122 | DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), | ||
123 | }; | ||
124 | let mut res = FxHashMap::default(); | ||
125 | res.insert(file_id, Some(range)); | ||
126 | return SearchScope::new(res); | ||
127 | } | ||
128 | |||
129 | let vis = self.visibility(db); | ||
130 | |||
131 | if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) { | ||
132 | let module: Module = module.into(); | ||
133 | let mut res = FxHashMap::default(); | ||
134 | |||
135 | let mut to_visit = vec![module]; | ||
136 | let mut is_first = true; | ||
137 | while let Some(module) = to_visit.pop() { | ||
138 | let src = module.definition_source(db); | ||
139 | let file_id = src.file_id.original_file(db); | ||
140 | match src.value { | ||
141 | ModuleSource::Module(m) => { | ||
142 | if is_first { | ||
143 | let range = Some(m.syntax().text_range()); | ||
144 | res.insert(file_id, range); | ||
145 | } else { | ||
146 | // We have already added the enclosing file to the search scope, | ||
147 | // so do nothing. | ||
148 | } | ||
149 | } | ||
150 | ModuleSource::SourceFile(_) => { | ||
151 | res.insert(file_id, None); | ||
152 | } | ||
153 | }; | ||
154 | is_first = false; | ||
155 | to_visit.extend(module.children(db)); | ||
156 | } | ||
157 | |||
158 | return SearchScope::new(res); | ||
159 | } | ||
160 | |||
161 | if let Some(Visibility::Public) = vis { | ||
162 | let source_root_id = db.file_source_root(file_id); | ||
163 | let source_root = db.source_root(source_root_id); | ||
164 | let mut res = source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>(); | ||
165 | |||
166 | let krate = module.krate(); | ||
167 | for rev_dep in krate.reverse_dependencies(db) { | ||
168 | let root_file = rev_dep.root_file(db); | ||
169 | let source_root_id = db.file_source_root(root_file); | ||
170 | let source_root = db.source_root(source_root_id); | ||
171 | res.extend(source_root.iter().map(|id| (id, None))); | ||
172 | } | ||
173 | return SearchScope::new(res); | ||
174 | } | ||
175 | |||
176 | let mut res = FxHashMap::default(); | ||
177 | let range = match module_src.value { | ||
178 | ModuleSource::Module(m) => Some(m.syntax().text_range()), | ||
179 | ModuleSource::SourceFile(_) => None, | ||
180 | }; | ||
181 | res.insert(file_id, range); | ||
182 | SearchScope::new(res) | ||
183 | } | ||
184 | |||
185 | pub fn find_usages( | ||
186 | &self, | ||
187 | sema: &Semantics<RootDatabase>, | ||
188 | search_scope: Option<SearchScope>, | ||
189 | ) -> Vec<Reference> { | ||
190 | let _p = profile("Definition::find_usages"); | ||
191 | |||
192 | let search_scope = { | ||
193 | let base = self.search_scope(sema.db); | ||
194 | match search_scope { | ||
195 | None => base, | ||
196 | Some(scope) => base.intersection(&scope), | ||
197 | } | ||
198 | }; | ||
199 | |||
200 | let name = match self.name(sema.db) { | ||
201 | None => return Vec::new(), | ||
202 | Some(it) => it.to_string(), | ||
203 | }; | ||
204 | |||
205 | let pat = name.as_str(); | ||
206 | let mut refs = vec![]; | ||
207 | |||
208 | for (file_id, search_range) in search_scope { | ||
209 | let text = sema.db.file_text(file_id); | ||
210 | let search_range = | ||
211 | search_range.unwrap_or(TextRange::up_to(TextSize::of(text.as_str()))); | ||
212 | |||
213 | let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); | ||
214 | |||
215 | for (idx, _) in text.match_indices(pat) { | ||
216 | let offset: TextSize = idx.try_into().unwrap(); | ||
217 | if !search_range.contains_inclusive(offset) { | ||
218 | continue; | ||
219 | } | ||
220 | |||
221 | let name_ref: ast::NameRef = | ||
222 | if let Some(name_ref) = sema.find_node_at_offset_with_descend(&tree, offset) { | ||
223 | name_ref | ||
224 | } else { | ||
225 | continue; | ||
226 | }; | ||
227 | |||
228 | match classify_name_ref(&sema, &name_ref) { | ||
229 | Some(NameRefClass::Definition(def)) if &def == self => { | ||
230 | let kind = if is_record_lit_name_ref(&name_ref) | ||
231 | || is_call_expr_name_ref(&name_ref) | ||
232 | { | ||
233 | ReferenceKind::StructLiteral | ||
234 | } else { | ||
235 | ReferenceKind::Other | ||
236 | }; | ||
237 | |||
238 | let file_range = sema.original_range(name_ref.syntax()); | ||
239 | refs.push(Reference { | ||
240 | file_range, | ||
241 | kind, | ||
242 | access: reference_access(&def, &name_ref), | ||
243 | }); | ||
244 | } | ||
245 | Some(NameRefClass::FieldShorthand { local, field }) => { | ||
246 | match self { | ||
247 | Definition::Field(_) if &field == self => refs.push(Reference { | ||
248 | file_range: sema.original_range(name_ref.syntax()), | ||
249 | kind: ReferenceKind::FieldShorthandForField, | ||
250 | access: reference_access(&field, &name_ref), | ||
251 | }), | ||
252 | Definition::Local(l) if &local == l => refs.push(Reference { | ||
253 | file_range: sema.original_range(name_ref.syntax()), | ||
254 | kind: ReferenceKind::FieldShorthandForLocal, | ||
255 | access: reference_access(&Definition::Local(local), &name_ref), | ||
256 | }), | ||
257 | |||
258 | _ => {} // not a usage | ||
259 | }; | ||
260 | } | ||
261 | _ => {} // not a usage | ||
262 | } | ||
263 | } | ||
264 | } | ||
265 | refs | ||
266 | } | ||
267 | } | ||
268 | |||
269 | fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> { | ||
270 | // Only Locals and Fields have accesses for now. | ||
271 | match def { | ||
272 | Definition::Local(_) | Definition::Field(_) => {} | ||
273 | _ => return None, | ||
274 | }; | ||
275 | |||
276 | let mode = name_ref.syntax().ancestors().find_map(|node| { | ||
277 | match_ast! { | ||
278 | match (node) { | ||
279 | ast::BinExpr(expr) => { | ||
280 | if expr.op_kind()?.is_assignment() { | ||
281 | // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). | ||
282 | // FIXME: This is not terribly accurate. | ||
283 | if let Some(lhs) = expr.lhs() { | ||
284 | if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { | ||
285 | return Some(ReferenceAccess::Write); | ||
286 | } | ||
287 | } | ||
288 | } | ||
289 | Some(ReferenceAccess::Read) | ||
290 | }, | ||
291 | _ => None | ||
292 | } | ||
293 | } | ||
294 | }); | ||
295 | |||
296 | // Default Locals and Fields to read | ||
297 | mode.or(Some(ReferenceAccess::Read)) | ||
298 | } | ||
299 | |||
300 | fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { | ||
301 | name_ref | ||
302 | .syntax() | ||
303 | .ancestors() | ||
304 | .find_map(ast::CallExpr::cast) | ||
305 | .and_then(|c| match c.expr()? { | ||
306 | ast::Expr::PathExpr(p) => { | ||
307 | Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) | ||
308 | } | ||
309 | _ => None, | ||
310 | }) | ||
311 | .unwrap_or(false) | ||
312 | } | ||
313 | |||
314 | fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { | ||
315 | name_ref | ||
316 | .syntax() | ||
317 | .ancestors() | ||
318 | .find_map(ast::RecordExpr::cast) | ||
319 | .and_then(|l| l.path()) | ||
320 | .and_then(|p| p.segment()) | ||
321 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | ||
322 | .unwrap_or(false) | ||
323 | } | ||