aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_db
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-03-04 11:14:48 +0000
committerAleksey Kladov <[email protected]>2020-03-04 11:22:47 +0000
commitf79719b8ae4d1929acaa940802a1e293f7dd7a6b (patch)
treea793e25b0037d61df3470ec59f09ea823d56d593 /crates/ra_ide_db
parent2638bec66cb05ff51a0804181a47546a0f55afbf (diff)
Move find_refs_to_def
Diffstat (limited to 'crates/ra_ide_db')
-rw-r--r--crates/ra_ide_db/Cargo.toml1
-rw-r--r--crates/ra_ide_db/src/search.rs149
2 files changed, 147 insertions, 3 deletions
diff --git a/crates/ra_ide_db/Cargo.toml b/crates/ra_ide_db/Cargo.toml
index 7ff1a536e..52f0f23df 100644
--- a/crates/ra_ide_db/Cargo.toml
+++ b/crates/ra_ide_db/Cargo.toml
@@ -16,6 +16,7 @@ rayon = "1.3.0"
16fst = { version = "0.3.5", default-features = false } 16fst = { version = "0.3.5", default-features = false }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18superslice = "1.0.0" 18superslice = "1.0.0"
19once_cell = "1.3.1"
19 20
20ra_syntax = { path = "../ra_syntax" } 21ra_syntax = { path = "../ra_syntax" }
21ra_text_edit = { path = "../ra_text_edit" } 22ra_text_edit = { path = "../ra_text_edit" }
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.
5use std::mem; 5use std::mem;
6 6
7use hir::{DefWithBody, HasSource, ModuleSource}; 7use hir::{DefWithBody, HasSource, ModuleSource, Semantics};
8use once_cell::unsync::Lazy;
8use ra_db::{FileId, FileRange, SourceDatabaseExt}; 9use ra_db::{FileId, FileRange, SourceDatabaseExt};
9use ra_prof::profile; 10use ra_prof::profile;
10use ra_syntax::{AstNode, TextRange}; 11use ra_syntax::{
12 algo::find_node_at_offset, ast, match_ast, AstNode, TextRange, TextUnit, TokenAtOffset,
13};
11use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
12 15
13use crate::{defs::Definition, RootDatabase}; 16use crate::{
17 defs::{classify_name_ref, Definition},
18 RootDatabase,
19};
14 20
15#[derive(Debug, Clone)] 21#[derive(Debug, Clone)]
16pub struct Reference { 22pub 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
174pub 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
255fn 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
286fn 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
300fn 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}