aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_db/src/search.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_db/src/search.rs')
-rw-r--r--crates/ra_ide_db/src/search.rs323
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
7use std::{convert::TryInto, mem};
8
9use hir::{DefWithBody, HasSource, Module, ModuleSource, Semantics, Visibility};
10use once_cell::unsync::Lazy;
11use ra_db::{FileId, FileRange, SourceDatabaseExt};
12use ra_prof::profile;
13use ra_syntax::{ast, match_ast, AstNode, TextRange, TextSize};
14use rustc_hash::FxHashMap;
15
16use crate::{
17 defs::{classify_name_ref, Definition, NameRefClass},
18 RootDatabase,
19};
20
21#[derive(Debug, Clone)]
22pub struct Reference {
23 pub file_range: FileRange,
24 pub kind: ReferenceKind,
25 pub access: Option<ReferenceAccess>,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum ReferenceKind {
30 FieldShorthandForField,
31 FieldShorthandForLocal,
32 StructLiteral,
33 Other,
34}
35
36#[derive(Debug, Copy, Clone, PartialEq)]
37pub 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.
46pub struct SearchScope {
47 entries: FxHashMap<FileId, Option<TextRange>>,
48}
49
50impl 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
99impl 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
108impl 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
269fn 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
300fn 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
314fn 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}