aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_ide/Cargo.toml1
-rw-r--r--crates/ra_ide/src/references.rs143
-rw-r--r--crates/ra_ide_db/Cargo.toml1
-rw-r--r--crates/ra_ide_db/src/search.rs149
5 files changed, 153 insertions, 142 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 87c288346..4ad436b2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1060,6 +1060,7 @@ version = "0.1.0"
1060dependencies = [ 1060dependencies = [
1061 "fst", 1061 "fst",
1062 "log", 1062 "log",
1063 "once_cell",
1063 "ra_db", 1064 "ra_db",
1064 "ra_hir", 1065 "ra_hir",
1065 "ra_prof", 1066 "ra_prof",
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index 7625fc8c8..adee7c493 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -19,6 +19,7 @@ join_to_string = "0.1.3"
19log = "0.4.8" 19log = "0.4.8"
20rustc-hash = "1.1.0" 20rustc-hash = "1.1.0"
21rand = { version = "0.7.3", features = ["small_rng"] } 21rand = { version = "0.7.3", features = ["small_rng"] }
22# TODO: check if can remove
22once_cell = "1.3.1" 23once_cell = "1.3.1"
23 24
24ra_syntax = { path = "../ra_syntax" } 25ra_syntax = { path = "../ra_syntax" }
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index ee065b6f9..abecca2bb 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -13,8 +13,6 @@ mod rename;
13mod search_scope; 13mod search_scope;
14 14
15use hir::Semantics; 15use hir::Semantics;
16use once_cell::unsync::Lazy;
17use ra_db::SourceDatabaseExt;
18use ra_ide_db::{ 16use ra_ide_db::{
19 defs::{classify_name, classify_name_ref, Definition}, 17 defs::{classify_name, classify_name_ref, Definition},
20 RootDatabase, 18 RootDatabase,
@@ -23,15 +21,16 @@ use ra_prof::profile;
23use ra_syntax::{ 21use ra_syntax::{
24 algo::find_node_at_offset, 22 algo::find_node_at_offset,
25 ast::{self, NameOwner}, 23 ast::{self, NameOwner},
26 match_ast, AstNode, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset, 24 AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
27}; 25};
28use test_utils::tested_by;
29 26
30use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
31 28
32pub(crate) use self::rename::rename; 29pub(crate) use self::rename::rename;
33 30
34pub use ra_ide_db::search::{Reference, ReferenceAccess, ReferenceKind, SearchScope}; 31pub use ra_ide_db::search::{
32 find_refs_to_def, Reference, ReferenceAccess, ReferenceKind, SearchScope,
33};
35 34
36#[derive(Debug, Clone)] 35#[derive(Debug, Clone)]
37pub struct ReferenceSearchResult { 36pub struct ReferenceSearchResult {
@@ -122,84 +121,6 @@ pub(crate) fn find_all_refs(
122 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) 121 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
123} 122}
124 123
125pub(crate) fn find_refs_to_def(
126 db: &RootDatabase,
127 def: &Definition,
128 search_scope: Option<SearchScope>,
129) -> Vec<Reference> {
130 let _p = profile("find_refs_to_def");
131
132 let search_scope = {
133 let base = SearchScope::for_def(&def, db);
134 match search_scope {
135 None => base,
136 Some(scope) => base.intersection(&scope),
137 }
138 };
139
140 let name = match def.name(db) {
141 None => return Vec::new(),
142 Some(it) => it.to_string(),
143 };
144
145 let pat = name.as_str();
146 let mut refs = vec![];
147
148 for (file_id, search_range) in search_scope {
149 let text = db.file_text(file_id);
150 let search_range =
151 search_range.unwrap_or(TextRange::offset_len(0.into(), TextUnit::of_str(&text)));
152
153 let sema = Semantics::new(db);
154 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
155
156 for (idx, _) in text.match_indices(pat) {
157 let offset = TextUnit::from_usize(idx);
158 if !search_range.contains_inclusive(offset) {
159 tested_by!(search_filters_by_range);
160 continue;
161 }
162
163 let name_ref =
164 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&tree, offset) {
165 name_ref
166 } else {
167 // Handle macro token cases
168 let token = match tree.token_at_offset(offset) {
169 TokenAtOffset::None => continue,
170 TokenAtOffset::Single(t) => t,
171 TokenAtOffset::Between(_, t) => t,
172 };
173 let expanded = sema.descend_into_macros(token);
174 match ast::NameRef::cast(expanded.parent()) {
175 Some(name_ref) => name_ref,
176 _ => continue,
177 }
178 };
179
180 if let Some(d) = classify_name_ref(&sema, &name_ref) {
181 let d = d.definition();
182 if &d == def {
183 let kind =
184 if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) {
185 ReferenceKind::StructLiteral
186 } else {
187 ReferenceKind::Other
188 };
189
190 let file_range = sema.original_range(name_ref.syntax());
191 refs.push(Reference {
192 file_range,
193 kind,
194 access: reference_access(&d, &name_ref),
195 });
196 }
197 }
198 }
199 }
200 refs
201}
202
203fn find_name( 124fn find_name(
204 sema: &Semantics<RootDatabase>, 125 sema: &Semantics<RootDatabase>,
205 syntax: &SyntaxNode, 126 syntax: &SyntaxNode,
@@ -236,48 +157,6 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio
236 None 157 None
237} 158}
238 159
239fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
240 // Only Locals and Fields have accesses for now.
241 match def {
242 Definition::Local(_) | Definition::StructField(_) => {}
243 _ => return None,
244 };
245
246 let mode = name_ref.syntax().ancestors().find_map(|node| {
247 match_ast! {
248 match (node) {
249 ast::BinExpr(expr) => {
250 if expr.op_kind()?.is_assignment() {
251 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
252 // FIXME: This is not terribly accurate.
253 if let Some(lhs) = expr.lhs() {
254 if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() {
255 return Some(ReferenceAccess::Write);
256 }
257 }
258 }
259 Some(ReferenceAccess::Read)
260 },
261 _ => {None}
262 }
263 }
264 });
265
266 // Default Locals and Fields to read
267 mode.or(Some(ReferenceAccess::Read))
268}
269
270fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
271 name_ref
272 .syntax()
273 .ancestors()
274 .find_map(ast::RecordLit::cast)
275 .and_then(|l| l.path())
276 .and_then(|p| p.segment())
277 .map(|p| p.name_ref().as_ref() == Some(name_ref))
278 .unwrap_or(false)
279}
280
281fn get_struct_def_name_for_struc_litetal_search( 160fn get_struct_def_name_for_struc_litetal_search(
282 syntax: &SyntaxNode, 161 syntax: &SyntaxNode,
283 position: FilePosition, 162 position: FilePosition,
@@ -296,20 +175,6 @@ fn get_struct_def_name_for_struc_litetal_search(
296 None 175 None
297} 176}
298 177
299fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool {
300 name_ref
301 .syntax()
302 .ancestors()
303 .find_map(ast::CallExpr::cast)
304 .and_then(|c| match c.expr()? {
305 ast::Expr::PathExpr(p) => {
306 Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref))
307 }
308 _ => None,
309 })
310 .unwrap_or(false)
311}
312
313#[cfg(test)] 178#[cfg(test)]
314mod tests { 179mod tests {
315 use test_utils::covers; 180 use test_utils::covers;
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}