aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-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.rs11
-rw-r--r--crates/ra_ide/src/diagnostics.rs36
-rw-r--r--crates/ra_ide/src/display/navigation_target.rs2
-rw-r--r--crates/ra_ide/src/expand.rs5
-rw-r--r--crates/ra_ide/src/lib.rs22
-rw-r--r--crates/ra_ide/src/references.rs205
-rw-r--r--crates/ra_ide/src/references/rename.rs8
9 files changed, 594 insertions, 47 deletions
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 387a9cafb..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 {
@@ -201,7 +202,7 @@ impl RootDatabase {
201 libraries.push(library.root_id); 202 libraries.push(library.root_id);
202 self.set_source_root_with_durability( 203 self.set_source_root_with_durability(
203 library.root_id, 204 library.root_id,
204 Default::default(), 205 Arc::new(SourceRoot::new_library()),
205 Durability::HIGH, 206 Durability::HIGH,
206 ); 207 );
207 self.set_library_symbols_with_durability( 208 self.set_library_symbols_with_durability(
@@ -273,7 +274,7 @@ impl RootDatabase {
273 self.query(hir::db::BodyWithSourceMapQuery).sweep(sweep); 274 self.query(hir::db::BodyWithSourceMapQuery).sweep(sweep);
274 275
275 self.query(hir::db::ExprScopesQuery).sweep(sweep); 276 self.query(hir::db::ExprScopesQuery).sweep(sweep);
276 self.query(hir::db::InferQuery).sweep(sweep); 277 self.query(hir::db::DoInferQuery).sweep(sweep);
277 self.query(hir::db::BodyQuery).sweep(sweep); 278 self.query(hir::db::BodyQuery).sweep(sweep);
278 } 279 }
279 280
@@ -309,7 +310,7 @@ impl RootDatabase {
309 hir::db::EnumDataQuery 310 hir::db::EnumDataQuery
310 hir::db::TraitDataQuery 311 hir::db::TraitDataQuery
311 hir::db::RawItemsQuery 312 hir::db::RawItemsQuery
312 hir::db::CrateDefMapQuery 313 hir::db::ComputeCrateDefMapQuery
313 hir::db::GenericParamsQuery 314 hir::db::GenericParamsQuery
314 hir::db::FunctionDataQuery 315 hir::db::FunctionDataQuery
315 hir::db::TypeAliasDataQuery 316 hir::db::TypeAliasDataQuery
@@ -320,7 +321,7 @@ impl RootDatabase {
320 hir::db::LangItemQuery 321 hir::db::LangItemQuery
321 hir::db::DocumentationQuery 322 hir::db::DocumentationQuery
322 hir::db::ExprScopesQuery 323 hir::db::ExprScopesQuery
323 hir::db::InferQuery 324 hir::db::DoInferQuery
324 hir::db::TyQuery 325 hir::db::TyQuery
325 hir::db::ValueTyQuery 326 hir::db::ValueTyQuery
326 hir::db::FieldTypesQuery 327 hir::db::FieldTypesQuery
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index c50a70d99..478368529 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -64,22 +64,36 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
64 }) 64 })
65 }) 65 })
66 .on::<hir::diagnostics::MissingFields, _>(|d| { 66 .on::<hir::diagnostics::MissingFields, _>(|d| {
67 let mut field_list = d.ast(db); 67 // Note that although we could add a diagnostics to
68 for f in d.missed_fields.iter() { 68 // fill the missing tuple field, e.g :
69 let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); 69 // `struct A(usize);`
70 field_list = field_list.append_field(&field); 70 // `let a = A { 0: () }`
71 } 71 // but it is uncommon usage and it should not be encouraged.
72 72 let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
73 let mut builder = TextEditBuilder::default(); 73 None
74 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); 74 } else {
75 let mut field_list = d.ast(db);
76 for f in d.missed_fields.iter() {
77 let field =
78 make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
79 field_list = field_list.append_field(&field);
80 }
81
82 let mut builder = TextEditBuilder::default();
83 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
84
85 Some(SourceChange::source_file_edit_from(
86 "fill struct fields",
87 file_id,
88 builder.finish(),
89 ))
90 };
75 91
76 let fix =
77 SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish());
78 res.borrow_mut().push(Diagnostic { 92 res.borrow_mut().push(Diagnostic {
79 range: d.highlight_range(), 93 range: d.highlight_range(),
80 message: d.message(), 94 message: d.message(),
81 severity: Severity::Error, 95 severity: Severity::Error,
82 fix: Some(fix), 96 fix,
83 }) 97 })
84 }) 98 })
85 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 99 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
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/expand.rs b/crates/ra_ide/src/expand.rs
index 7a22bb0a4..b82259a3d 100644
--- a/crates/ra_ide/src/expand.rs
+++ b/crates/ra_ide/src/expand.rs
@@ -76,14 +76,15 @@ pub(crate) fn descend_into_macros(
76) -> InFile<SyntaxToken> { 76) -> InFile<SyntaxToken> {
77 let src = InFile::new(file_id.into(), token); 77 let src = InFile::new(file_id.into(), token);
78 78
79 let source_analyzer =
80 hir::SourceAnalyzer::new(db, src.with_value(src.value.parent()).as_ref(), None);
81
79 successors(Some(src), |token| { 82 successors(Some(src), |token| {
80 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; 83 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
81 let tt = macro_call.token_tree()?; 84 let tt = macro_call.token_tree()?;
82 if !token.value.text_range().is_subrange(&tt.syntax().text_range()) { 85 if !token.value.text_range().is_subrange(&tt.syntax().text_range()) {
83 return None; 86 return None;
84 } 87 }
85 let source_analyzer =
86 hir::SourceAnalyzer::new(db, token.with_value(token.value.parent()).as_ref(), None);
87 let exp = source_analyzer.expand(db, token.with_value(&macro_call))?; 88 let exp = source_analyzer.expand(db, token.with_value(&macro_call))?;
88 exp.map_token_down(db, token.as_ref()) 89 exp.map_token_down(db, token.as_ref())
89 }) 90 })
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 779a81b2c..7b187eba3 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,7 @@ 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::{Reference, ReferenceKind, ReferenceSearchResult, SearchScope},
77 runnables::{Runnable, RunnableKind}, 79 runnables::{Runnable, RunnableKind},
78 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 80 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
79 syntax_highlighting::HighlightedRange, 81 syntax_highlighting::HighlightedRange,
@@ -412,6 +414,24 @@ impl Analysis {
412 self.with_db(|db| call_info::call_info(db, position)) 414 self.with_db(|db| call_info::call_info(db, position))
413 } 415 }
414 416
417 /// Computes call hierarchy candidates for the given file position.
418 pub fn call_hierarchy(
419 &self,
420 position: FilePosition,
421 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
422 self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
423 }
424
425 /// Computes incoming calls for the given file position.
426 pub fn incoming_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
427 self.with_db(|db| call_hierarchy::incoming_calls(db, position))
428 }
429
430 /// Computes incoming calls for the given file position.
431 pub fn outgoing_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
432 self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
433 }
434
415 /// Returns a `mod name;` declaration which created the current module. 435 /// Returns a `mod name;` declaration which created the current module.
416 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { 436 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
417 self.with_db(|db| parent_module::parent_module(db, position)) 437 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..5a3ec4eb9 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -18,7 +18,10 @@ 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, ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextUnit,
23 TokenAtOffset,
24};
22 25
23use crate::{ 26use crate::{
24 db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, 27 db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo,
@@ -35,7 +38,20 @@ pub use self::search_scope::SearchScope;
35#[derive(Debug, Clone)] 38#[derive(Debug, Clone)]
36pub struct ReferenceSearchResult { 39pub struct ReferenceSearchResult {
37 declaration: NavigationTarget, 40 declaration: NavigationTarget,
38 references: Vec<FileRange>, 41 declaration_kind: ReferenceKind,
42 references: Vec<Reference>,
43}
44
45#[derive(Debug, Clone)]
46pub struct Reference {
47 pub file_range: FileRange,
48 pub kind: ReferenceKind,
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub enum ReferenceKind {
53 StructLiteral,
54 Other,
39} 55}
40 56
41impl ReferenceSearchResult { 57impl ReferenceSearchResult {
@@ -43,7 +59,7 @@ impl ReferenceSearchResult {
43 &self.declaration 59 &self.declaration
44 } 60 }
45 61
46 pub fn references(&self) -> &[FileRange] { 62 pub fn references(&self) -> &[Reference] {
47 &self.references 63 &self.references
48 } 64 }
49 65
@@ -58,12 +74,18 @@ impl ReferenceSearchResult {
58// allow turning ReferenceSearchResult into an iterator 74// allow turning ReferenceSearchResult into an iterator
59// over FileRanges 75// over FileRanges
60impl IntoIterator for ReferenceSearchResult { 76impl IntoIterator for ReferenceSearchResult {
61 type Item = FileRange; 77 type Item = Reference;
62 type IntoIter = std::vec::IntoIter<FileRange>; 78 type IntoIter = std::vec::IntoIter<Reference>;
63 79
64 fn into_iter(mut self) -> Self::IntoIter { 80 fn into_iter(mut self) -> Self::IntoIter {
65 let mut v = Vec::with_capacity(self.len()); 81 let mut v = Vec::with_capacity(self.len());
66 v.push(FileRange { file_id: self.declaration.file_id(), range: self.declaration.range() }); 82 v.push(Reference {
83 file_range: FileRange {
84 file_id: self.declaration.file_id(),
85 range: self.declaration.range(),
86 },
87 kind: self.declaration_kind,
88 });
67 v.append(&mut self.references); 89 v.append(&mut self.references);
68 v.into_iter() 90 v.into_iter()
69 } 91 }
@@ -71,11 +93,24 @@ impl IntoIterator for ReferenceSearchResult {
71 93
72pub(crate) fn find_all_refs( 94pub(crate) fn find_all_refs(
73 db: &RootDatabase, 95 db: &RootDatabase,
74 position: FilePosition, 96 mut position: FilePosition,
75 search_scope: Option<SearchScope>, 97 search_scope: Option<SearchScope>,
76) -> Option<RangeInfo<ReferenceSearchResult>> { 98) -> Option<RangeInfo<ReferenceSearchResult>> {
77 let parse = db.parse(position.file_id); 99 let parse = db.parse(position.file_id);
78 let syntax = parse.tree().syntax().clone(); 100 let syntax = parse.tree().syntax().clone();
101
102 let token = syntax.token_at_offset(position.offset);
103 let mut search_kind = ReferenceKind::Other;
104
105 if let TokenAtOffset::Between(ref left, ref right) = token {
106 if (right.kind() == SyntaxKind::L_CURLY || right.kind() == SyntaxKind::L_PAREN)
107 && left.kind() != SyntaxKind::IDENT
108 {
109 position = FilePosition { offset: left.text_range().start(), ..position };
110 search_kind = ReferenceKind::StructLiteral;
111 }
112 }
113
79 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; 114 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
80 115
81 let declaration = match def.kind { 116 let declaration = match def.kind {
@@ -96,9 +131,15 @@ pub(crate) fn find_all_refs(
96 } 131 }
97 }; 132 };
98 133
99 let references = process_definition(db, def, name, search_scope); 134 let references = process_definition(db, def, name, search_scope)
135 .into_iter()
136 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
137 .collect();
100 138
101 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) 139 Some(RangeInfo::new(
140 range,
141 ReferenceSearchResult { declaration, references, declaration_kind: ReferenceKind::Other },
142 ))
102} 143}
103 144
104fn find_name<'a>( 145fn find_name<'a>(
@@ -122,7 +163,7 @@ fn process_definition(
122 def: NameDefinition, 163 def: NameDefinition,
123 name: String, 164 name: String,
124 scope: SearchScope, 165 scope: SearchScope,
125) -> Vec<FileRange> { 166) -> Vec<Reference> {
126 let _p = profile("process_definition"); 167 let _p = profile("process_definition");
127 168
128 let pat = name.as_str(); 169 let pat = name.as_str();
@@ -146,7 +187,21 @@ fn process_definition(
146 } 187 }
147 if let Some(d) = classify_name_ref(db, InFile::new(file_id.into(), &name_ref)) { 188 if let Some(d) = classify_name_ref(db, InFile::new(file_id.into(), &name_ref)) {
148 if d == def { 189 if d == def {
149 refs.push(FileRange { file_id, range }); 190 let kind = if name_ref
191 .syntax()
192 .ancestors()
193 .find_map(ast::RecordLit::cast)
194 .and_then(|l| l.path())
195 .and_then(|p| p.segment())
196 .and_then(|p| p.name_ref())
197 .map(|n| n == name_ref)
198 .unwrap_or(false)
199 {
200 ReferenceKind::StructLiteral
201 } else {
202 ReferenceKind::Other
203 };
204 refs.push(Reference { file_range: FileRange { file_id, range }, kind });
150 } 205 }
151 } 206 }
152 } 207 }
@@ -159,10 +214,33 @@ fn process_definition(
159mod tests { 214mod tests {
160 use crate::{ 215 use crate::{
161 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, 216 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
162 ReferenceSearchResult, SearchScope, 217 Reference, ReferenceKind, ReferenceSearchResult, SearchScope,
163 }; 218 };
164 219
165 #[test] 220 #[test]
221 fn test_struct_literal() {
222 let code = r#"
223 struct Foo <|>{
224 a: i32,
225 }
226 impl Foo {
227 fn f() -> i32 { 42 }
228 }
229 fn main() {
230 let f: Foo;
231 f = Foo {a: Foo::f()};
232 }"#;
233
234 let refs = get_all_refs(code);
235 check_result(
236 refs,
237 "Foo STRUCT_DEF FileId(1) [5; 39) [12; 15)",
238 ReferenceKind::Other,
239 &["FileId(1) [142; 145) StructLiteral"],
240 );
241 }
242
243 #[test]
166 fn test_find_all_refs_for_local() { 244 fn test_find_all_refs_for_local() {
167 let code = r#" 245 let code = r#"
168 fn main() { 246 fn main() {
@@ -178,7 +256,17 @@ mod tests {
178 }"#; 256 }"#;
179 257
180 let refs = get_all_refs(code); 258 let refs = get_all_refs(code);
181 assert_eq!(refs.len(), 5); 259 check_result(
260 refs,
261 "i BIND_PAT FileId(1) [33; 34)",
262 ReferenceKind::Other,
263 &[
264 "FileId(1) [67; 68) Other",
265 "FileId(1) [71; 72) Other",
266 "FileId(1) [101; 102) Other",
267 "FileId(1) [127; 128) Other",
268 ],
269 );
182 } 270 }
183 271
184 #[test] 272 #[test]
@@ -189,7 +277,12 @@ mod tests {
189 }"#; 277 }"#;
190 278
191 let refs = get_all_refs(code); 279 let refs = get_all_refs(code);
192 assert_eq!(refs.len(), 2); 280 check_result(
281 refs,
282 "i BIND_PAT FileId(1) [12; 13)",
283 ReferenceKind::Other,
284 &["FileId(1) [38; 39) Other"],
285 );
193 } 286 }
194 287
195 #[test] 288 #[test]
@@ -200,7 +293,12 @@ mod tests {
200 }"#; 293 }"#;
201 294
202 let refs = get_all_refs(code); 295 let refs = get_all_refs(code);
203 assert_eq!(refs.len(), 2); 296 check_result(
297 refs,
298 "i BIND_PAT FileId(1) [12; 13)",
299 ReferenceKind::Other,
300 &["FileId(1) [38; 39) Other"],
301 );
204 } 302 }
205 303
206 #[test] 304 #[test]
@@ -217,7 +315,12 @@ mod tests {
217 "#; 315 "#;
218 316
219 let refs = get_all_refs(code); 317 let refs = get_all_refs(code);
220 assert_eq!(refs.len(), 2); 318 check_result(
319 refs,
320 "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74)",
321 ReferenceKind::Other,
322 &["FileId(1) [152; 156) Other"],
323 );
221 } 324 }
222 325
223 #[test] 326 #[test]
@@ -231,7 +334,7 @@ mod tests {
231 "#; 334 "#;
232 335
233 let refs = get_all_refs(code); 336 let refs = get_all_refs(code);
234 assert_eq!(refs.len(), 1); 337 check_result(refs, "f FN_DEF FileId(1) [88; 104) [91; 92)", ReferenceKind::Other, &[]);
235 } 338 }
236 339
237 #[test] 340 #[test]
@@ -246,7 +349,7 @@ mod tests {
246 "#; 349 "#;
247 350
248 let refs = get_all_refs(code); 351 let refs = get_all_refs(code);
249 assert_eq!(refs.len(), 1); 352 check_result(refs, "B ENUM_VARIANT FileId(1) [83; 84) [83; 84)", ReferenceKind::Other, &[]);
250 } 353 }
251 354
252 #[test] 355 #[test]
@@ -285,7 +388,12 @@ mod tests {
285 388
286 let (analysis, pos) = analysis_and_position(code); 389 let (analysis, pos) = analysis_and_position(code);
287 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 390 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
288 assert_eq!(refs.len(), 3); 391 check_result(
392 refs,
393 "Foo STRUCT_DEF FileId(2) [16; 50) [27; 30)",
394 ReferenceKind::Other,
395 &["FileId(1) [52; 55) StructLiteral", "FileId(3) [77; 80) StructLiteral"],
396 );
289 } 397 }
290 398
291 // `mod foo;` is not in the results because `foo` is an `ast::Name`. 399 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
@@ -311,7 +419,12 @@ mod tests {
311 419
312 let (analysis, pos) = analysis_and_position(code); 420 let (analysis, pos) = analysis_and_position(code);
313 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 421 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
314 assert_eq!(refs.len(), 2); 422 check_result(
423 refs,
424 "foo SOURCE_FILE FileId(2) [0; 35)",
425 ReferenceKind::Other,
426 &["FileId(1) [13; 16) Other"],
427 );
315 } 428 }
316 429
317 #[test] 430 #[test]
@@ -336,7 +449,12 @@ mod tests {
336 449
337 let (analysis, pos) = analysis_and_position(code); 450 let (analysis, pos) = analysis_and_position(code);
338 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 451 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
339 assert_eq!(refs.len(), 3); 452 check_result(
453 refs,
454 "Foo STRUCT_DEF FileId(3) [0; 41) [18; 21)",
455 ReferenceKind::Other,
456 &["FileId(2) [20; 23) Other", "FileId(2) [46; 49) StructLiteral"],
457 );
340 } 458 }
341 459
342 #[test] 460 #[test]
@@ -360,11 +478,21 @@ mod tests {
360 let analysis = mock.analysis(); 478 let analysis = mock.analysis();
361 479
362 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 480 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
363 assert_eq!(refs.len(), 3); 481 check_result(
482 refs,
483 "quux FN_DEF FileId(1) [18; 34) [25; 29)",
484 ReferenceKind::Other,
485 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"],
486 );
364 487
365 let refs = 488 let refs =
366 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); 489 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
367 assert_eq!(refs.len(), 2); 490 check_result(
491 refs,
492 "quux FN_DEF FileId(1) [18; 34) [25; 29)",
493 ReferenceKind::Other,
494 &["FileId(3) [16; 20) Other"],
495 );
368 } 496 }
369 497
370 #[test] 498 #[test]
@@ -379,11 +507,40 @@ mod tests {
379 }"#; 507 }"#;
380 508
381 let refs = get_all_refs(code); 509 let refs = get_all_refs(code);
382 assert_eq!(refs.len(), 3); 510 check_result(
511 refs,
512 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48)",
513 ReferenceKind::Other,
514 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"],
515 );
383 } 516 }
384 517
385 fn get_all_refs(text: &str) -> ReferenceSearchResult { 518 fn get_all_refs(text: &str) -> ReferenceSearchResult {
386 let (analysis, position) = single_file_with_position(text); 519 let (analysis, position) = single_file_with_position(text);
387 analysis.find_all_refs(position, None).unwrap().unwrap() 520 analysis.find_all_refs(position, None).unwrap().unwrap()
388 } 521 }
522
523 fn check_result(
524 res: ReferenceSearchResult,
525 expected_decl: &str,
526 decl_kind: ReferenceKind,
527 expected_refs: &[&str],
528 ) {
529 res.declaration().assert_match(expected_decl);
530 assert_eq!(res.declaration_kind, decl_kind);
531
532 assert_eq!(res.references.len(), expected_refs.len());
533 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i]));
534 }
535
536 impl Reference {
537 fn debug_render(&self) -> String {
538 format!("{:?} {:?} {:?}", self.file_range.file_id, self.file_range.range, self.kind)
539 }
540
541 fn assert_match(&self, expected: &str) {
542 let actual = self.debug_render();
543 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
544 }
545 }
389} 546}
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() {