aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/Cargo.toml1
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs337
-rw-r--r--crates/ra_ide/src/call_info.rs15
-rw-r--r--crates/ra_ide/src/change.rs3
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs2
-rw-r--r--crates/ra_ide/src/feature_flags.rs1
-rw-r--r--crates/ra_ide/src/hover.rs20
-rw-r--r--crates/ra_ide/src/lib.rs24
-rw-r--r--crates/ra_ide/src/references.rs352
-rw-r--r--crates/ra_ide/src/references/rename.rs8
10 files changed, 730 insertions, 33 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml
index e3439ae31..2c9f9dce0 100644
--- a/crates/ra_ide/Cargo.toml
+++ b/crates/ra_ide/Cargo.toml
@@ -13,6 +13,7 @@ wasm = []
13[dependencies] 13[dependencies]
14either = "1.5" 14either = "1.5"
15format-buf = "1.0.0" 15format-buf = "1.0.0"
16indexmap = "1.3.0"
16itertools = "0.8.0" 17itertools = "0.8.0"
17join_to_string = "0.1.3" 18join_to_string = "0.1.3"
18log = "0.4.5" 19log = "0.4.5"
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs
new file mode 100644
index 000000000..1cb712e32
--- /dev/null
+++ b/crates/ra_ide/src/call_hierarchy.rs
@@ -0,0 +1,337 @@
1//! Entry point for call-hierarchy
2
3use indexmap::IndexMap;
4
5use hir::db::AstDatabase;
6use ra_syntax::{
7 ast::{self, DocCommentsOwner},
8 match_ast, AstNode, TextRange,
9};
10
11use crate::{
12 call_info::FnCallNode,
13 db::RootDatabase,
14 display::{ShortLabel, ToNav},
15 expand::descend_into_macros,
16 goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
17};
18
19#[derive(Debug, Clone)]
20pub struct CallItem {
21 pub target: NavigationTarget,
22 pub ranges: Vec<TextRange>,
23}
24
25impl CallItem {
26 #[cfg(test)]
27 pub(crate) fn assert_match(&self, expected: &str) {
28 let actual = self.debug_render();
29 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
30 }
31
32 #[cfg(test)]
33 pub(crate) fn debug_render(&self) -> String {
34 format!("{} : {:?}", self.target.debug_render(), self.ranges)
35 }
36}
37
38pub(crate) fn call_hierarchy(
39 db: &RootDatabase,
40 position: FilePosition,
41) -> Option<RangeInfo<Vec<NavigationTarget>>> {
42 goto_definition::goto_definition(db, position)
43}
44
45pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
46 // 1. Find all refs
47 // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
48 // 3. Add ranges relative to the start of the fndef.
49 let refs = references::find_all_refs(db, position, None)?;
50
51 let mut calls = CallLocations::default();
52
53 for reference in refs.info.references() {
54 let file_id = reference.file_range.file_id;
55 let file = db.parse_or_expand(file_id.into())?;
56 let token = file.token_at_offset(reference.file_range.range.start()).next()?;
57 let token = descend_into_macros(db, file_id, token);
58 let syntax = token.value.parent();
59
60 // This target is the containing function
61 if let Some(nav) = syntax.ancestors().find_map(|node| {
62 match_ast! {
63 match node {
64 ast::FnDef(it) => {
65 Some(NavigationTarget::from_named(
66 db,
67 token.with_value(&it),
68 it.doc_comment_text(),
69 it.short_label(),
70 ))
71 },
72 _ => { None },
73 }
74 }
75 }) {
76 let relative_range = reference.file_range.range;
77 calls.add(&nav, relative_range);
78 }
79 }
80
81 Some(calls.into_items())
82}
83
84pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
85 let file_id = position.file_id;
86 let file = db.parse_or_expand(file_id.into())?;
87 let token = file.token_at_offset(position.offset).next()?;
88 let token = descend_into_macros(db, file_id, token);
89 let syntax = token.value.parent();
90
91 let mut calls = CallLocations::default();
92
93 syntax
94 .descendants()
95 .filter_map(|node| FnCallNode::with_node_exact(&node))
96 .filter_map(|call_node| {
97 let name_ref = call_node.name_ref()?;
98 let name_ref = token.with_value(name_ref.syntax());
99
100 let analyzer = hir::SourceAnalyzer::new(db, name_ref, None);
101
102 if let Some(func_target) = match &call_node {
103 FnCallNode::CallExpr(expr) => {
104 //FIXME: Type::as_callable is broken
105 let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
106 match callable_def {
107 hir::CallableDef::FunctionId(it) => {
108 let fn_def: hir::Function = it.into();
109 let nav = fn_def.to_nav(db);
110 Some(nav)
111 }
112 _ => None,
113 }
114 }
115 FnCallNode::MethodCallExpr(expr) => {
116 let function = analyzer.resolve_method_call(&expr)?;
117 Some(function.to_nav(db))
118 }
119 FnCallNode::MacroCallExpr(expr) => {
120 let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?;
121 Some(macro_def.to_nav(db))
122 }
123 } {
124 Some((func_target.clone(), name_ref.value.text_range()))
125 } else {
126 None
127 }
128 })
129 .for_each(|(nav, range)| calls.add(&nav, range));
130
131 Some(calls.into_items())
132}
133
134#[derive(Default)]
135struct CallLocations {
136 funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
137}
138
139impl CallLocations {
140 fn add(&mut self, target: &NavigationTarget, range: TextRange) {
141 self.funcs.entry(target.clone()).or_default().push(range);
142 }
143
144 fn into_items(self) -> Vec<CallItem> {
145 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use ra_db::FilePosition;
152
153 use crate::mock_analysis::analysis_and_position;
154
155 fn check_hierarchy(
156 fixture: &str,
157 expected: &str,
158 expected_incoming: &[&str],
159 expected_outgoing: &[&str],
160 ) {
161 let (analysis, pos) = analysis_and_position(fixture);
162
163 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
164 assert_eq!(navs.len(), 1);
165 let nav = navs.pop().unwrap();
166 nav.assert_match(expected);
167
168 let item_pos = FilePosition { file_id: nav.file_id(), offset: nav.range().start() };
169 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
170 assert_eq!(incoming_calls.len(), expected_incoming.len());
171
172 for call in 0..incoming_calls.len() {
173 incoming_calls[call].assert_match(expected_incoming[call]);
174 }
175
176 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
177 assert_eq!(outgoing_calls.len(), expected_outgoing.len());
178
179 for call in 0..outgoing_calls.len() {
180 outgoing_calls[call].assert_match(expected_outgoing[call]);
181 }
182 }
183
184 #[test]
185 fn test_call_hierarchy_on_ref() {
186 check_hierarchy(
187 r#"
188 //- /lib.rs
189 fn callee() {}
190 fn caller() {
191 call<|>ee();
192 }
193 "#,
194 "callee FN_DEF FileId(1) [0; 14) [3; 9)",
195 &["caller FN_DEF FileId(1) [15; 44) [18; 24) : [[33; 39)]"],
196 &[],
197 );
198 }
199
200 #[test]
201 fn test_call_hierarchy_on_def() {
202 check_hierarchy(
203 r#"
204 //- /lib.rs
205 fn call<|>ee() {}
206 fn caller() {
207 callee();
208 }
209 "#,
210 "callee FN_DEF FileId(1) [0; 14) [3; 9)",
211 &["caller FN_DEF FileId(1) [15; 44) [18; 24) : [[33; 39)]"],
212 &[],
213 );
214 }
215
216 #[test]
217 fn test_call_hierarchy_in_same_fn() {
218 check_hierarchy(
219 r#"
220 //- /lib.rs
221 fn callee() {}
222 fn caller() {
223 call<|>ee();
224 callee();
225 }
226 "#,
227 "callee FN_DEF FileId(1) [0; 14) [3; 9)",
228 &["caller FN_DEF FileId(1) [15; 58) [18; 24) : [[33; 39), [47; 53)]"],
229 &[],
230 );
231 }
232
233 #[test]
234 fn test_call_hierarchy_in_different_fn() {
235 check_hierarchy(
236 r#"
237 //- /lib.rs
238 fn callee() {}
239 fn caller1() {
240 call<|>ee();
241 }
242
243 fn caller2() {
244 callee();
245 }
246 "#,
247 "callee FN_DEF FileId(1) [0; 14) [3; 9)",
248 &[
249 "caller1 FN_DEF FileId(1) [15; 45) [18; 25) : [[34; 40)]",
250 "caller2 FN_DEF FileId(1) [46; 76) [49; 56) : [[65; 71)]",
251 ],
252 &[],
253 );
254 }
255
256 #[test]
257 fn test_call_hierarchy_in_different_files() {
258 check_hierarchy(
259 r#"
260 //- /lib.rs
261 mod foo;
262 use foo::callee;
263
264 fn caller() {
265 call<|>ee();
266 }
267
268 //- /foo/mod.rs
269 pub fn callee() {}
270 "#,
271 "callee FN_DEF FileId(2) [0; 18) [7; 13)",
272 &["caller FN_DEF FileId(1) [26; 55) [29; 35) : [[44; 50)]"],
273 &[],
274 );
275 }
276
277 #[test]
278 fn test_call_hierarchy_outgoing() {
279 check_hierarchy(
280 r#"
281 //- /lib.rs
282 fn callee() {}
283 fn call<|>er() {
284 callee();
285 callee();
286 }
287 "#,
288 "caller FN_DEF FileId(1) [15; 58) [18; 24)",
289 &[],
290 &["callee FN_DEF FileId(1) [0; 14) [3; 9) : [[33; 39), [47; 53)]"],
291 );
292 }
293
294 #[test]
295 fn test_call_hierarchy_outgoing_in_different_files() {
296 check_hierarchy(
297 r#"
298 //- /lib.rs
299 mod foo;
300 use foo::callee;
301
302 fn call<|>er() {
303 callee();
304 }
305
306 //- /foo/mod.rs
307 pub fn callee() {}
308 "#,
309 "caller FN_DEF FileId(1) [26; 55) [29; 35)",
310 &[],
311 &["callee FN_DEF FileId(2) [0; 18) [7; 13) : [[44; 50)]"],
312 );
313 }
314
315 #[test]
316 fn test_call_hierarchy_incoming_outgoing() {
317 check_hierarchy(
318 r#"
319 //- /lib.rs
320 fn caller1() {
321 call<|>er2();
322 }
323
324 fn caller2() {
325 caller3();
326 }
327
328 fn caller3() {
329
330 }
331 "#,
332 "caller2 FN_DEF FileId(1) [32; 63) [35; 42)",
333 &["caller1 FN_DEF FileId(1) [0; 31) [3; 10) : [[19; 26)]"],
334 &["caller3 FN_DEF FileId(1) [64; 80) [67; 74) : [[51; 58)]"],
335 );
336 }
337}
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs
index 2c2b6fa48..a7023529b 100644
--- a/crates/ra_ide/src/call_info.rs
+++ b/crates/ra_ide/src/call_info.rs
@@ -88,7 +88,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
88} 88}
89 89
90#[derive(Debug)] 90#[derive(Debug)]
91enum FnCallNode { 91pub(crate) enum FnCallNode {
92 CallExpr(ast::CallExpr), 92 CallExpr(ast::CallExpr),
93 MethodCallExpr(ast::MethodCallExpr), 93 MethodCallExpr(ast::MethodCallExpr),
94 MacroCallExpr(ast::MacroCall), 94 MacroCallExpr(ast::MacroCall),
@@ -108,7 +108,18 @@ impl FnCallNode {
108 }) 108 })
109 } 109 }
110 110
111 fn name_ref(&self) -> Option<ast::NameRef> { 111 pub(crate) fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> {
112 match_ast! {
113 match node {
114 ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) },
115 ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) },
116 ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) },
117 _ => { None },
118 }
119 }
120 }
121
122 pub(crate) fn name_ref(&self) -> Option<ast::NameRef> {
112 match self { 123 match self {
113 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { 124 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
114 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, 125 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
diff --git a/crates/ra_ide/src/change.rs b/crates/ra_ide/src/change.rs
index 8b197d642..b0aa2c8e0 100644
--- a/crates/ra_ide/src/change.rs
+++ b/crates/ra_ide/src/change.rs
@@ -176,7 +176,8 @@ impl RootDatabase {
176 if !change.new_roots.is_empty() { 176 if !change.new_roots.is_empty() {
177 let mut local_roots = Vec::clone(&self.local_roots()); 177 let mut local_roots = Vec::clone(&self.local_roots());
178 for (root_id, is_local) in change.new_roots { 178 for (root_id, is_local) in change.new_roots {
179 let root = if is_local { SourceRoot::new() } else { SourceRoot::new_library() }; 179 let root =
180 if is_local { SourceRoot::new_local() } else { SourceRoot::new_library() };
180 let durability = durability(&root); 181 let durability = durability(&root);
181 self.set_source_root_with_durability(root_id, Arc::new(root), durability); 182 self.set_source_root_with_durability(root_id, Arc::new(root), durability);
182 if is_local { 183 if is_local {
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs
index b9ae67828..f2e45fa31 100644
--- a/crates/ra_ide/src/display/navigation_target.rs
+++ b/crates/ra_ide/src/display/navigation_target.rs
@@ -19,7 +19,7 @@ use super::short_label::ShortLabel;
19/// 19///
20/// Typically, a `NavigationTarget` corresponds to some element in the source 20/// Typically, a `NavigationTarget` corresponds to some element in the source
21/// code, like a function or a struct, but this is not strictly required. 21/// code, like a function or a struct, but this is not strictly required.
22#[derive(Debug, Clone)] 22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct NavigationTarget { 23pub struct NavigationTarget {
24 file_id: FileId, 24 file_id: FileId,
25 name: SmolStr, 25 name: SmolStr,
diff --git a/crates/ra_ide/src/feature_flags.rs b/crates/ra_ide/src/feature_flags.rs
index de4ae513d..85617640d 100644
--- a/crates/ra_ide/src/feature_flags.rs
+++ b/crates/ra_ide/src/feature_flags.rs
@@ -56,6 +56,7 @@ impl Default for FeatureFlags {
56 ("completion.insertion.add-call-parenthesis", true), 56 ("completion.insertion.add-call-parenthesis", true),
57 ("completion.enable-postfix", true), 57 ("completion.enable-postfix", true),
58 ("notifications.workspace-loaded", true), 58 ("notifications.workspace-loaded", true),
59 ("notifications.cargo-toml-not-found", true),
59 ]) 60 ])
60 } 61 }
61} 62}
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 35e39f965..5548681f1 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -128,7 +128,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, name_kind: NameKind) -> Option<S
128 hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), 128 hir::ModuleDef::TypeAlias(it) => from_def_source(db, it),
129 hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), 129 hir::ModuleDef::BuiltinType(it) => Some(it.to_string()),
130 }, 130 },
131 Local(_) => None, 131 Local(it) => Some(rust_code_markup(it.ty(db).display_truncated(db, None).to_string())),
132 TypeParam(_) | SelfType(_) => { 132 TypeParam(_) | SelfType(_) => {
133 // FIXME: Hover for generic param 133 // FIXME: Hover for generic param
134 None 134 None
@@ -174,6 +174,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
174 .value 174 .value
175 .ancestors() 175 .ancestors()
176 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; 176 .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?;
177
178 // The following logic will not work if token is coming from a macro
177 let frange = FileRange { file_id: position.file_id, range: node.text_range() }; 179 let frange = FileRange { file_id: position.file_id, range: node.text_range() };
178 res.extend(type_of(db, frange).map(rust_code_markup)); 180 res.extend(type_of(db, frange).map(rust_code_markup));
179 if res.is_empty() { 181 if res.is_empty() {
@@ -729,4 +731,20 @@ fn func(foo: i32) { if true { <|>foo; }; }
729 &["fn foo()"], 731 &["fn foo()"],
730 ); 732 );
731 } 733 }
734
735 #[test]
736 fn test_hover_through_expr_in_macro() {
737 check_hover_result(
738 "
739 //- /lib.rs
740 macro_rules! id {
741 ($($tt:tt)*) => { $($tt)* }
742 }
743 fn foo(bar:u32) {
744 let a = id!(ba<|>r);
745 }
746 ",
747 &["u32"],
748 );
749 }
732} 750}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 779a81b2c..4d8deb21c 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -24,6 +24,7 @@ mod goto_definition;
24mod goto_type_definition; 24mod goto_type_definition;
25mod extend_selection; 25mod extend_selection;
26mod hover; 26mod hover;
27mod call_hierarchy;
27mod call_info; 28mod call_info;
28mod syntax_highlighting; 29mod syntax_highlighting;
29mod parent_module; 30mod parent_module;
@@ -62,6 +63,7 @@ use crate::{db::LineIndexDatabase, display::ToNav, symbol_index::FileSymbol};
62 63
63pub use crate::{ 64pub use crate::{
64 assists::{Assist, AssistId}, 65 assists::{Assist, AssistId},
66 call_hierarchy::CallItem,
65 change::{AnalysisChange, LibraryData}, 67 change::{AnalysisChange, LibraryData},
66 completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, 68 completion::{CompletionItem, CompletionItemKind, InsertTextFormat},
67 diagnostics::Severity, 69 diagnostics::Severity,
@@ -73,7 +75,9 @@ pub use crate::{
73 inlay_hints::{InlayHint, InlayKind}, 75 inlay_hints::{InlayHint, InlayKind},
74 line_index::{LineCol, LineIndex}, 76 line_index::{LineCol, LineIndex},
75 line_index_utils::translate_offset_with_edit, 77 line_index_utils::translate_offset_with_edit,
76 references::{ReferenceSearchResult, SearchScope}, 78 references::{
79 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope,
80 },
77 runnables::{Runnable, RunnableKind}, 81 runnables::{Runnable, RunnableKind},
78 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 82 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
79 syntax_highlighting::HighlightedRange, 83 syntax_highlighting::HighlightedRange,
@@ -412,6 +416,24 @@ impl Analysis {
412 self.with_db(|db| call_info::call_info(db, position)) 416 self.with_db(|db| call_info::call_info(db, position))
413 } 417 }
414 418
419 /// Computes call hierarchy candidates for the given file position.
420 pub fn call_hierarchy(
421 &self,
422 position: FilePosition,
423 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
424 self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
425 }
426
427 /// Computes incoming calls for the given file position.
428 pub fn incoming_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
429 self.with_db(|db| call_hierarchy::incoming_calls(db, position))
430 }
431
432 /// Computes incoming calls for the given file position.
433 pub fn outgoing_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
434 self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
435 }
436
415 /// Returns a `mod name;` declaration which created the current module. 437 /// Returns a `mod name;` declaration which created the current module.
416 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { 438 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
417 self.with_db(|db| parent_module::parent_module(db, position)) 439 self.with_db(|db| parent_module::parent_module(db, position))
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index e3ecde50d..4e52e0e7b 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -18,7 +18,11 @@ use hir::InFile;
18use once_cell::unsync::Lazy; 18use once_cell::unsync::Lazy;
19use ra_db::{SourceDatabase, SourceDatabaseExt}; 19use ra_db::{SourceDatabase, SourceDatabaseExt};
20use ra_prof::profile; 20use ra_prof::profile;
21use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; 21use ra_syntax::{
22 algo::find_node_at_offset,
23 ast::{self, NameOwner},
24 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset,
25};
22 26
23use crate::{ 27use crate::{
24 db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, 28 db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo,
@@ -34,16 +38,46 @@ pub use self::search_scope::SearchScope;
34 38
35#[derive(Debug, Clone)] 39#[derive(Debug, Clone)]
36pub struct ReferenceSearchResult { 40pub struct ReferenceSearchResult {
37 declaration: NavigationTarget, 41 declaration: Declaration,
38 references: Vec<FileRange>, 42 references: Vec<Reference>,
43}
44
45#[derive(Debug, Clone)]
46pub struct Declaration {
47 pub nav: NavigationTarget,
48 pub kind: ReferenceKind,
49 pub access: Option<ReferenceAccess>,
50}
51
52#[derive(Debug, Clone)]
53pub struct Reference {
54 pub file_range: FileRange,
55 pub kind: ReferenceKind,
56 pub access: Option<ReferenceAccess>,
57}
58
59#[derive(Debug, Clone, PartialEq)]
60pub enum ReferenceKind {
61 StructLiteral,
62 Other,
63}
64
65#[derive(Debug, Copy, Clone, PartialEq)]
66pub enum ReferenceAccess {
67 Read,
68 Write,
39} 69}
40 70
41impl ReferenceSearchResult { 71impl ReferenceSearchResult {
42 pub fn declaration(&self) -> &NavigationTarget { 72 pub fn declaration(&self) -> &Declaration {
43 &self.declaration 73 &self.declaration
44 } 74 }
45 75
46 pub fn references(&self) -> &[FileRange] { 76 pub fn decl_target(&self) -> &NavigationTarget {
77 &self.declaration.nav
78 }
79
80 pub fn references(&self) -> &[Reference] {
47 &self.references 81 &self.references
48 } 82 }
49 83
@@ -56,14 +90,21 @@ impl ReferenceSearchResult {
56} 90}
57 91
58// allow turning ReferenceSearchResult into an iterator 92// allow turning ReferenceSearchResult into an iterator
59// over FileRanges 93// over References
60impl IntoIterator for ReferenceSearchResult { 94impl IntoIterator for ReferenceSearchResult {
61 type Item = FileRange; 95 type Item = Reference;
62 type IntoIter = std::vec::IntoIter<FileRange>; 96 type IntoIter = std::vec::IntoIter<Reference>;
63 97
64 fn into_iter(mut self) -> Self::IntoIter { 98 fn into_iter(mut self) -> Self::IntoIter {
65 let mut v = Vec::with_capacity(self.len()); 99 let mut v = Vec::with_capacity(self.len());
66 v.push(FileRange { file_id: self.declaration.file_id(), range: self.declaration.range() }); 100 v.push(Reference {
101 file_range: FileRange {
102 file_id: self.declaration.nav.file_id(),
103 range: self.declaration.nav.range(),
104 },
105 kind: self.declaration.kind,
106 access: self.declaration.access,
107 });
67 v.append(&mut self.references); 108 v.append(&mut self.references);
68 v.into_iter() 109 v.into_iter()
69 } 110 }
@@ -71,11 +112,24 @@ impl IntoIterator for ReferenceSearchResult {
71 112
72pub(crate) fn find_all_refs( 113pub(crate) fn find_all_refs(
73 db: &RootDatabase, 114 db: &RootDatabase,
74 position: FilePosition, 115 mut position: FilePosition,
75 search_scope: Option<SearchScope>, 116 search_scope: Option<SearchScope>,
76) -> Option<RangeInfo<ReferenceSearchResult>> { 117) -> Option<RangeInfo<ReferenceSearchResult>> {
77 let parse = db.parse(position.file_id); 118 let parse = db.parse(position.file_id);
78 let syntax = parse.tree().syntax().clone(); 119 let syntax = parse.tree().syntax().clone();
120
121 let token = syntax.token_at_offset(position.offset);
122 let mut search_kind = ReferenceKind::Other;
123
124 if let TokenAtOffset::Between(ref left, ref right) = token {
125 if (right.kind() == SyntaxKind::L_CURLY || right.kind() == SyntaxKind::L_PAREN)
126 && left.kind() != SyntaxKind::IDENT
127 {
128 position = FilePosition { offset: left.text_range().start(), ..position };
129 search_kind = ReferenceKind::StructLiteral;
130 }
131 }
132
79 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; 133 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
80 134
81 let declaration = match def.kind { 135 let declaration = match def.kind {
@@ -96,7 +150,18 @@ pub(crate) fn find_all_refs(
96 } 150 }
97 }; 151 };
98 152
99 let references = process_definition(db, def, name, search_scope); 153 let decl_range = declaration.range();
154
155 let declaration = Declaration {
156 nav: declaration,
157 kind: ReferenceKind::Other,
158 access: decl_access(&def.kind, &name, &syntax, decl_range),
159 };
160
161 let references = process_definition(db, def, name, search_scope)
162 .into_iter()
163 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
164 .collect();
100 165
101 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) 166 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
102} 167}
@@ -122,7 +187,7 @@ fn process_definition(
122 def: NameDefinition, 187 def: NameDefinition,
123 name: String, 188 name: String,
124 scope: SearchScope, 189 scope: SearchScope,
125) -> Vec<FileRange> { 190) -> Vec<Reference> {
126 let _p = profile("process_definition"); 191 let _p = profile("process_definition");
127 192
128 let pat = name.as_str(); 193 let pat = name.as_str();
@@ -146,7 +211,26 @@ fn process_definition(
146 } 211 }
147 if let Some(d) = classify_name_ref(db, InFile::new(file_id.into(), &name_ref)) { 212 if let Some(d) = classify_name_ref(db, InFile::new(file_id.into(), &name_ref)) {
148 if d == def { 213 if d == def {
149 refs.push(FileRange { file_id, range }); 214 let kind = if name_ref
215 .syntax()
216 .ancestors()
217 .find_map(ast::RecordLit::cast)
218 .and_then(|l| l.path())
219 .and_then(|p| p.segment())
220 .and_then(|p| p.name_ref())
221 .map(|n| n == name_ref)
222 .unwrap_or(false)
223 {
224 ReferenceKind::StructLiteral
225 } else {
226 ReferenceKind::Other
227 };
228
229 refs.push(Reference {
230 file_range: FileRange { file_id, range },
231 kind,
232 access: reference_access(&d.kind, &name_ref),
233 });
150 } 234 }
151 } 235 }
152 } 236 }
@@ -155,14 +239,94 @@ fn process_definition(
155 refs 239 refs
156} 240}
157 241
242fn decl_access(
243 kind: &NameKind,
244 name: &str,
245 syntax: &SyntaxNode,
246 range: TextRange,
247) -> Option<ReferenceAccess> {
248 match kind {
249 NameKind::Local(_) | NameKind::Field(_) => {}
250 _ => return None,
251 };
252
253 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
254 if let Some(_) = stmt.initializer() {
255 let pat = stmt.pat()?;
256 match pat {
257 ast::Pat::BindPat(it) => {
258 if it.name()?.text().as_str() == name {
259 return Some(ReferenceAccess::Write);
260 }
261 }
262 _ => {}
263 }
264 }
265
266 None
267}
268
269fn reference_access(kind: &NameKind, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
270 // Only Locals and Fields have accesses for now.
271 match kind {
272 NameKind::Local(_) | NameKind::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 return 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
158#[cfg(test)] 300#[cfg(test)]
159mod tests { 301mod tests {
160 use crate::{ 302 use crate::{
161 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, 303 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
162 ReferenceSearchResult, SearchScope, 304 Declaration, Reference, ReferenceSearchResult, SearchScope,
163 }; 305 };
164 306
165 #[test] 307 #[test]
308 fn test_struct_literal() {
309 let code = r#"
310 struct Foo <|>{
311 a: i32,
312 }
313 impl Foo {
314 fn f() -> i32 { 42 }
315 }
316 fn main() {
317 let f: Foo;
318 f = Foo {a: Foo::f()};
319 }"#;
320
321 let refs = get_all_refs(code);
322 check_result(
323 refs,
324 "Foo STRUCT_DEF FileId(1) [5; 39) [12; 15) Other",
325 &["FileId(1) [142; 145) StructLiteral"],
326 );
327 }
328
329 #[test]
166 fn test_find_all_refs_for_local() { 330 fn test_find_all_refs_for_local() {
167 let code = r#" 331 let code = r#"
168 fn main() { 332 fn main() {
@@ -178,7 +342,16 @@ mod tests {
178 }"#; 342 }"#;
179 343
180 let refs = get_all_refs(code); 344 let refs = get_all_refs(code);
181 assert_eq!(refs.len(), 5); 345 check_result(
346 refs,
347 "i BIND_PAT FileId(1) [33; 34) Other Write",
348 &[
349 "FileId(1) [67; 68) Other Write",
350 "FileId(1) [71; 72) Other Read",
351 "FileId(1) [101; 102) Other Write",
352 "FileId(1) [127; 128) Other Write",
353 ],
354 );
182 } 355 }
183 356
184 #[test] 357 #[test]
@@ -189,7 +362,11 @@ mod tests {
189 }"#; 362 }"#;
190 363
191 let refs = get_all_refs(code); 364 let refs = get_all_refs(code);
192 assert_eq!(refs.len(), 2); 365 check_result(
366 refs,
367 "i BIND_PAT FileId(1) [12; 13) Other",
368 &["FileId(1) [38; 39) Other Read"],
369 );
193 } 370 }
194 371
195 #[test] 372 #[test]
@@ -200,7 +377,11 @@ mod tests {
200 }"#; 377 }"#;
201 378
202 let refs = get_all_refs(code); 379 let refs = get_all_refs(code);
203 assert_eq!(refs.len(), 2); 380 check_result(
381 refs,
382 "i BIND_PAT FileId(1) [12; 13) Other",
383 &["FileId(1) [38; 39) Other Read"],
384 );
204 } 385 }
205 386
206 #[test] 387 #[test]
@@ -217,7 +398,11 @@ mod tests {
217 "#; 398 "#;
218 399
219 let refs = get_all_refs(code); 400 let refs = get_all_refs(code);
220 assert_eq!(refs.len(), 2); 401 check_result(
402 refs,
403 "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74) Other",
404 &["FileId(1) [152; 156) Other Read"],
405 );
221 } 406 }
222 407
223 #[test] 408 #[test]
@@ -231,7 +416,7 @@ mod tests {
231 "#; 416 "#;
232 417
233 let refs = get_all_refs(code); 418 let refs = get_all_refs(code);
234 assert_eq!(refs.len(), 1); 419 check_result(refs, "f FN_DEF FileId(1) [88; 104) [91; 92) Other", &[]);
235 } 420 }
236 421
237 #[test] 422 #[test]
@@ -246,7 +431,7 @@ mod tests {
246 "#; 431 "#;
247 432
248 let refs = get_all_refs(code); 433 let refs = get_all_refs(code);
249 assert_eq!(refs.len(), 1); 434 check_result(refs, "B ENUM_VARIANT FileId(1) [83; 84) [83; 84) Other", &[]);
250 } 435 }
251 436
252 #[test] 437 #[test]
@@ -285,7 +470,11 @@ mod tests {
285 470
286 let (analysis, pos) = analysis_and_position(code); 471 let (analysis, pos) = analysis_and_position(code);
287 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 472 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
288 assert_eq!(refs.len(), 3); 473 check_result(
474 refs,
475 "Foo STRUCT_DEF FileId(2) [16; 50) [27; 30) Other",
476 &["FileId(1) [52; 55) StructLiteral", "FileId(3) [77; 80) StructLiteral"],
477 );
289 } 478 }
290 479
291 // `mod foo;` is not in the results because `foo` is an `ast::Name`. 480 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
@@ -311,7 +500,11 @@ mod tests {
311 500
312 let (analysis, pos) = analysis_and_position(code); 501 let (analysis, pos) = analysis_and_position(code);
313 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 502 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
314 assert_eq!(refs.len(), 2); 503 check_result(
504 refs,
505 "foo SOURCE_FILE FileId(2) [0; 35) Other",
506 &["FileId(1) [13; 16) Other"],
507 );
315 } 508 }
316 509
317 #[test] 510 #[test]
@@ -336,7 +529,11 @@ mod tests {
336 529
337 let (analysis, pos) = analysis_and_position(code); 530 let (analysis, pos) = analysis_and_position(code);
338 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 531 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
339 assert_eq!(refs.len(), 3); 532 check_result(
533 refs,
534 "Foo STRUCT_DEF FileId(3) [0; 41) [18; 21) Other",
535 &["FileId(2) [20; 23) Other", "FileId(2) [46; 49) StructLiteral"],
536 );
340 } 537 }
341 538
342 #[test] 539 #[test]
@@ -360,11 +557,19 @@ mod tests {
360 let analysis = mock.analysis(); 557 let analysis = mock.analysis();
361 558
362 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 559 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
363 assert_eq!(refs.len(), 3); 560 check_result(
561 refs,
562 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
563 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"],
564 );
364 565
365 let refs = 566 let refs =
366 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); 567 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
367 assert_eq!(refs.len(), 2); 568 check_result(
569 refs,
570 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
571 &["FileId(3) [16; 20) Other"],
572 );
368 } 573 }
369 574
370 #[test] 575 #[test]
@@ -379,11 +584,106 @@ mod tests {
379 }"#; 584 }"#;
380 585
381 let refs = get_all_refs(code); 586 let refs = get_all_refs(code);
382 assert_eq!(refs.len(), 3); 587 check_result(
588 refs,
589 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48) Other",
590 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"],
591 );
592 }
593
594 #[test]
595 fn test_basic_highlight_read_write() {
596 let code = r#"
597 fn foo() {
598 let i<|> = 0;
599 i = i + 1;
600 }"#;
601
602 let refs = get_all_refs(code);
603 check_result(
604 refs,
605 "i BIND_PAT FileId(1) [36; 37) Other Write",
606 &["FileId(1) [55; 56) Other Write", "FileId(1) [59; 60) Other Read"],
607 );
608 }
609
610 #[test]
611 fn test_basic_highlight_field_read_write() {
612 let code = r#"
613 struct S {
614 f: u32,
615 }
616
617 fn foo() {
618 let mut s = S{f: 0};
619 s.f<|> = 0;
620 }"#;
621
622 let refs = get_all_refs(code);
623 check_result(
624 refs,
625 "f RECORD_FIELD_DEF FileId(1) [32; 38) [32; 33) Other",
626 &["FileId(1) [96; 97) Other Read", "FileId(1) [117; 118) Other Write"],
627 );
628 }
629
630 #[test]
631 fn test_basic_highlight_decl_no_write() {
632 let code = r#"
633 fn foo() {
634 let i<|>;
635 i = 1;
636 }"#;
637
638 let refs = get_all_refs(code);
639 check_result(
640 refs,
641 "i BIND_PAT FileId(1) [36; 37) Other",
642 &["FileId(1) [51; 52) Other Write"],
643 );
383 } 644 }
384 645
385 fn get_all_refs(text: &str) -> ReferenceSearchResult { 646 fn get_all_refs(text: &str) -> ReferenceSearchResult {
386 let (analysis, position) = single_file_with_position(text); 647 let (analysis, position) = single_file_with_position(text);
387 analysis.find_all_refs(position, None).unwrap().unwrap() 648 analysis.find_all_refs(position, None).unwrap().unwrap()
388 } 649 }
650
651 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
652 res.declaration().assert_match(expected_decl);
653 assert_eq!(res.references.len(), expected_refs.len());
654 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i]));
655 }
656
657 impl Declaration {
658 fn debug_render(&self) -> String {
659 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind);
660 if let Some(access) = self.access {
661 s.push_str(&format!(" {:?}", access));
662 }
663 s
664 }
665
666 fn assert_match(&self, expected: &str) {
667 let actual = self.debug_render();
668 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
669 }
670 }
671
672 impl Reference {
673 fn debug_render(&self) -> String {
674 let mut s = format!(
675 "{:?} {:?} {:?}",
676 self.file_range.file_id, self.file_range.range, self.kind
677 );
678 if let Some(access) = self.access {
679 s.push_str(&format!(" {:?}", access));
680 }
681 s
682 }
683
684 fn assert_match(&self, expected: &str) {
685 let actual = self.debug_render();
686 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
687 }
688 }
389} 689}
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index b804d5f6d..e02985dcd 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -110,7 +110,13 @@ fn rename_reference(
110 110
111 let edit = refs 111 let edit = refs
112 .into_iter() 112 .into_iter()
113 .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) 113 .map(|reference| {
114 source_edit_from_file_id_range(
115 reference.file_range.file_id,
116 reference.file_range.range,
117 new_name,
118 )
119 })
114 .collect::<Vec<_>>(); 120 .collect::<Vec<_>>();
115 121
116 if edit.is_empty() { 122 if edit.is_empty() {