diff options
Diffstat (limited to 'crates/ra_ide_db/src')
-rw-r--r-- | crates/ra_ide_db/src/search.rs | 149 |
1 files changed, 146 insertions, 3 deletions
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs index ca458388c..efd43f4c1 100644 --- a/crates/ra_ide_db/src/search.rs +++ b/crates/ra_ide_db/src/search.rs | |||
@@ -4,13 +4,19 @@ | |||
4 | //! e.g. for things like local variables. | 4 | //! e.g. for things like local variables. |
5 | use std::mem; | 5 | use std::mem; |
6 | 6 | ||
7 | use hir::{DefWithBody, HasSource, ModuleSource}; | 7 | use hir::{DefWithBody, HasSource, ModuleSource, Semantics}; |
8 | use once_cell::unsync::Lazy; | ||
8 | use ra_db::{FileId, FileRange, SourceDatabaseExt}; | 9 | use ra_db::{FileId, FileRange, SourceDatabaseExt}; |
9 | use ra_prof::profile; | 10 | use ra_prof::profile; |
10 | use ra_syntax::{AstNode, TextRange}; | 11 | use ra_syntax::{ |
12 | algo::find_node_at_offset, ast, match_ast, AstNode, TextRange, TextUnit, TokenAtOffset, | ||
13 | }; | ||
11 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
12 | 15 | ||
13 | use crate::{defs::Definition, RootDatabase}; | 16 | use crate::{ |
17 | defs::{classify_name_ref, Definition}, | ||
18 | RootDatabase, | ||
19 | }; | ||
14 | 20 | ||
15 | #[derive(Debug, Clone)] | 21 | #[derive(Debug, Clone)] |
16 | pub struct Reference { | 22 | pub struct Reference { |
@@ -164,3 +170,140 @@ impl IntoIterator for SearchScope { | |||
164 | self.entries.into_iter() | 170 | self.entries.into_iter() |
165 | } | 171 | } |
166 | } | 172 | } |
173 | |||
174 | pub fn find_refs_to_def( | ||
175 | db: &RootDatabase, | ||
176 | def: &Definition, | ||
177 | search_scope: Option<SearchScope>, | ||
178 | ) -> Vec<Reference> { | ||
179 | let _p = profile("find_refs_to_def"); | ||
180 | |||
181 | let search_scope = { | ||
182 | let base = SearchScope::for_def(&def, db); | ||
183 | match search_scope { | ||
184 | None => base, | ||
185 | Some(scope) => base.intersection(&scope), | ||
186 | } | ||
187 | }; | ||
188 | |||
189 | let name = match def.name(db) { | ||
190 | None => return Vec::new(), | ||
191 | Some(it) => it.to_string(), | ||
192 | }; | ||
193 | |||
194 | let pat = name.as_str(); | ||
195 | let mut refs = vec![]; | ||
196 | |||
197 | for (file_id, search_range) in search_scope { | ||
198 | let text = db.file_text(file_id); | ||
199 | let search_range = | ||
200 | search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text))); | ||
201 | |||
202 | let sema = Semantics::new(db); | ||
203 | let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); | ||
204 | |||
205 | for (idx, _) in text.match_indices(pat) { | ||
206 | let offset = TextUnit::from_usize(idx); | ||
207 | if !search_range.contains_inclusive(offset) { | ||
208 | // tested_by!(search_filters_by_range); | ||
209 | continue; | ||
210 | } | ||
211 | |||
212 | let name_ref = | ||
213 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&tree, offset) { | ||
214 | name_ref | ||
215 | } else { | ||
216 | // Handle macro token cases | ||
217 | let token = match tree.token_at_offset(offset) { | ||
218 | TokenAtOffset::None => continue, | ||
219 | TokenAtOffset::Single(t) => t, | ||
220 | TokenAtOffset::Between(_, t) => t, | ||
221 | }; | ||
222 | let expanded = sema.descend_into_macros(token); | ||
223 | match ast::NameRef::cast(expanded.parent()) { | ||
224 | Some(name_ref) => name_ref, | ||
225 | _ => continue, | ||
226 | } | ||
227 | }; | ||
228 | |||
229 | // FIXME: reuse sb | ||
230 | // See https://github.com/rust-lang/rust/pull/68198#issuecomment-574269098 | ||
231 | |||
232 | if let Some(d) = classify_name_ref(&sema, &name_ref) { | ||
233 | let d = d.definition(); | ||
234 | if &d == def { | ||
235 | let kind = | ||
236 | if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) { | ||
237 | ReferenceKind::StructLiteral | ||
238 | } else { | ||
239 | ReferenceKind::Other | ||
240 | }; | ||
241 | |||
242 | let file_range = sema.original_range(name_ref.syntax()); | ||
243 | refs.push(Reference { | ||
244 | file_range, | ||
245 | kind, | ||
246 | access: reference_access(&d, &name_ref), | ||
247 | }); | ||
248 | } | ||
249 | } | ||
250 | } | ||
251 | } | ||
252 | refs | ||
253 | } | ||
254 | |||
255 | fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> { | ||
256 | // Only Locals and Fields have accesses for now. | ||
257 | match def { | ||
258 | Definition::Local(_) | Definition::StructField(_) => {} | ||
259 | _ => return None, | ||
260 | }; | ||
261 | |||
262 | let mode = name_ref.syntax().ancestors().find_map(|node| { | ||
263 | match_ast! { | ||
264 | match (node) { | ||
265 | ast::BinExpr(expr) => { | ||
266 | if expr.op_kind()?.is_assignment() { | ||
267 | // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). | ||
268 | // FIXME: This is not terribly accurate. | ||
269 | if let Some(lhs) = expr.lhs() { | ||
270 | if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { | ||
271 | return Some(ReferenceAccess::Write); | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | Some(ReferenceAccess::Read) | ||
276 | }, | ||
277 | _ => {None} | ||
278 | } | ||
279 | } | ||
280 | }); | ||
281 | |||
282 | // Default Locals and Fields to read | ||
283 | mode.or(Some(ReferenceAccess::Read)) | ||
284 | } | ||
285 | |||
286 | fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { | ||
287 | name_ref | ||
288 | .syntax() | ||
289 | .ancestors() | ||
290 | .find_map(ast::CallExpr::cast) | ||
291 | .and_then(|c| match c.expr()? { | ||
292 | ast::Expr::PathExpr(p) => { | ||
293 | Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) | ||
294 | } | ||
295 | _ => None, | ||
296 | }) | ||
297 | .unwrap_or(false) | ||
298 | } | ||
299 | |||
300 | fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { | ||
301 | name_ref | ||
302 | .syntax() | ||
303 | .ancestors() | ||
304 | .find_map(ast::RecordLit::cast) | ||
305 | .and_then(|l| l.path()) | ||
306 | .and_then(|p| p.segment()) | ||
307 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | ||
308 | .unwrap_or(false) | ||
309 | } | ||