diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ra_ide/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide/src/references.rs | 143 | ||||
-rw-r--r-- | crates/ra_ide_db/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide_db/src/search.rs | 149 |
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" | |||
1060 | dependencies = [ | 1060 | dependencies = [ |
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" | |||
19 | log = "0.4.8" | 19 | log = "0.4.8" |
20 | rustc-hash = "1.1.0" | 20 | rustc-hash = "1.1.0" |
21 | rand = { version = "0.7.3", features = ["small_rng"] } | 21 | rand = { version = "0.7.3", features = ["small_rng"] } |
22 | # TODO: check if can remove | ||
22 | once_cell = "1.3.1" | 23 | once_cell = "1.3.1" |
23 | 24 | ||
24 | ra_syntax = { path = "../ra_syntax" } | 25 | ra_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; | |||
13 | mod search_scope; | 13 | mod search_scope; |
14 | 14 | ||
15 | use hir::Semantics; | 15 | use hir::Semantics; |
16 | use once_cell::unsync::Lazy; | ||
17 | use ra_db::SourceDatabaseExt; | ||
18 | use ra_ide_db::{ | 16 | use 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; | |||
23 | use ra_syntax::{ | 21 | use 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 | }; |
28 | use test_utils::tested_by; | ||
29 | 26 | ||
30 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; | 27 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; |
31 | 28 | ||
32 | pub(crate) use self::rename::rename; | 29 | pub(crate) use self::rename::rename; |
33 | 30 | ||
34 | pub use ra_ide_db::search::{Reference, ReferenceAccess, ReferenceKind, SearchScope}; | 31 | pub 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)] |
37 | pub struct ReferenceSearchResult { | 36 | pub 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 | ||
125 | pub(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 | |||
203 | fn find_name( | 124 | fn 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 | ||
239 | fn 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 | |||
270 | fn 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 | |||
281 | fn get_struct_def_name_for_struc_litetal_search( | 160 | fn 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 | ||
299 | fn 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)] |
314 | mod tests { | 179 | mod 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" | |||
16 | fst = { version = "0.3.5", default-features = false } | 16 | fst = { version = "0.3.5", default-features = false } |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
18 | superslice = "1.0.0" | 18 | superslice = "1.0.0" |
19 | once_cell = "1.3.1" | ||
19 | 20 | ||
20 | ra_syntax = { path = "../ra_syntax" } | 21 | ra_syntax = { path = "../ra_syntax" } |
21 | ra_text_edit = { path = "../ra_text_edit" } | 22 | ra_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. |
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 | } | ||