]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/call_hierarchy.rs
rename mock_analysis -> fixture
[rust.git] / crates / ide / src / call_hierarchy.rs
1 //! Entry point for call-hierarchy
2
3 use indexmap::IndexMap;
4
5 use hir::Semantics;
6 use ide_db::RootDatabase;
7 use syntax::{ast, match_ast, AstNode, TextRange};
8
9 use crate::{
10     call_info::FnCallNode, display::ToNav, goto_definition, references, FilePosition,
11     NavigationTarget, RangeInfo,
12 };
13
14 #[derive(Debug, Clone)]
15 pub struct CallItem {
16     pub target: NavigationTarget,
17     pub ranges: Vec<TextRange>,
18 }
19
20 impl CallItem {
21     #[cfg(test)]
22     pub(crate) fn assert_match(&self, expected: &str) {
23         let actual = self.debug_render();
24         test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
25     }
26
27     #[cfg(test)]
28     pub(crate) fn debug_render(&self) -> String {
29         format!("{} : {:?}", self.target.debug_render(), self.ranges)
30     }
31 }
32
33 pub(crate) fn call_hierarchy(
34     db: &RootDatabase,
35     position: FilePosition,
36 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
37     goto_definition::goto_definition(db, position)
38 }
39
40 pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
41     let sema = Semantics::new(db);
42
43     // 1. Find all refs
44     // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
45     // 3. Add ranges relative to the start of the fndef.
46     let refs = references::find_all_refs(&sema, position, None)?;
47
48     let mut calls = CallLocations::default();
49
50     for reference in refs.info.references() {
51         let file_id = reference.file_range.file_id;
52         let file = sema.parse(file_id);
53         let file = file.syntax();
54         let token = file.token_at_offset(reference.file_range.range.start()).next()?;
55         let token = sema.descend_into_macros(token);
56         let syntax = token.parent();
57
58         // This target is the containing function
59         if let Some(nav) = syntax.ancestors().find_map(|node| {
60             match_ast! {
61                 match node {
62                     ast::Fn(it) => {
63                         let def = sema.to_def(&it)?;
64                         Some(def.to_nav(sema.db))
65                     },
66                     _ => None,
67                 }
68             }
69         }) {
70             let relative_range = reference.file_range.range;
71             calls.add(&nav, relative_range);
72         }
73     }
74
75     Some(calls.into_items())
76 }
77
78 pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
79     let sema = Semantics::new(db);
80     let file_id = position.file_id;
81     let file = sema.parse(file_id);
82     let file = file.syntax();
83     let token = file.token_at_offset(position.offset).next()?;
84     let token = sema.descend_into_macros(token);
85     let syntax = token.parent();
86
87     let mut calls = CallLocations::default();
88
89     syntax
90         .descendants()
91         .filter_map(|node| FnCallNode::with_node_exact(&node))
92         .filter_map(|call_node| {
93             let name_ref = call_node.name_ref()?;
94
95             if let Some(func_target) = match &call_node {
96                 FnCallNode::CallExpr(expr) => {
97                     //FIXME: Type::as_callable is broken
98                     let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
99                     match callable.kind() {
100                         hir::CallableKind::Function(it) => {
101                             let fn_def: hir::Function = it.into();
102                             let nav = fn_def.to_nav(db);
103                             Some(nav)
104                         }
105                         _ => None,
106                     }
107                 }
108                 FnCallNode::MethodCallExpr(expr) => {
109                     let function = sema.resolve_method_call(&expr)?;
110                     Some(function.to_nav(db))
111                 }
112             } {
113                 Some((func_target, name_ref.syntax().text_range()))
114             } else {
115                 None
116             }
117         })
118         .for_each(|(nav, range)| calls.add(&nav, range));
119
120     Some(calls.into_items())
121 }
122
123 #[derive(Default)]
124 struct CallLocations {
125     funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
126 }
127
128 impl CallLocations {
129     fn add(&mut self, target: &NavigationTarget, range: TextRange) {
130         self.funcs.entry(target.clone()).or_default().push(range);
131     }
132
133     fn into_items(self) -> Vec<CallItem> {
134         self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
135     }
136 }
137
138 #[cfg(test)]
139 mod tests {
140     use base_db::FilePosition;
141
142     use crate::fixture;
143
144     fn check_hierarchy(
145         ra_fixture: &str,
146         expected: &str,
147         expected_incoming: &[&str],
148         expected_outgoing: &[&str],
149     ) {
150         let (analysis, pos) = fixture::position(ra_fixture);
151
152         let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
153         assert_eq!(navs.len(), 1);
154         let nav = navs.pop().unwrap();
155         nav.assert_match(expected);
156
157         let item_pos =
158             FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
159         let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
160         assert_eq!(incoming_calls.len(), expected_incoming.len());
161
162         for call in 0..incoming_calls.len() {
163             incoming_calls[call].assert_match(expected_incoming[call]);
164         }
165
166         let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
167         assert_eq!(outgoing_calls.len(), expected_outgoing.len());
168
169         for call in 0..outgoing_calls.len() {
170             outgoing_calls[call].assert_match(expected_outgoing[call]);
171         }
172     }
173
174     #[test]
175     fn test_call_hierarchy_on_ref() {
176         check_hierarchy(
177             r#"
178 //- /lib.rs
179 fn callee() {}
180 fn caller() {
181     call<|>ee();
182 }
183 "#,
184             "callee FN FileId(0) 0..14 3..9",
185             &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
186             &[],
187         );
188     }
189
190     #[test]
191     fn test_call_hierarchy_on_def() {
192         check_hierarchy(
193             r#"
194 //- /lib.rs
195 fn call<|>ee() {}
196 fn caller() {
197     callee();
198 }
199 "#,
200             "callee FN FileId(0) 0..14 3..9",
201             &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
202             &[],
203         );
204     }
205
206     #[test]
207     fn test_call_hierarchy_in_same_fn() {
208         check_hierarchy(
209             r#"
210 //- /lib.rs
211 fn callee() {}
212 fn caller() {
213     call<|>ee();
214     callee();
215 }
216 "#,
217             "callee FN FileId(0) 0..14 3..9",
218             &["caller FN FileId(0) 15..58 18..24 : [33..39, 47..53]"],
219             &[],
220         );
221     }
222
223     #[test]
224     fn test_call_hierarchy_in_different_fn() {
225         check_hierarchy(
226             r#"
227 //- /lib.rs
228 fn callee() {}
229 fn caller1() {
230     call<|>ee();
231 }
232
233 fn caller2() {
234     callee();
235 }
236 "#,
237             "callee FN FileId(0) 0..14 3..9",
238             &[
239                 "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
240                 "caller2 FN FileId(0) 47..77 50..57 : [66..72]",
241             ],
242             &[],
243         );
244     }
245
246     #[test]
247     fn test_call_hierarchy_in_tests_mod() {
248         check_hierarchy(
249             r#"
250 //- /lib.rs cfg:test
251 fn callee() {}
252 fn caller1() {
253     call<|>ee();
254 }
255
256 #[cfg(test)]
257 mod tests {
258     use super::*;
259
260     #[test]
261     fn test_caller() {
262         callee();
263     }
264 }
265 "#,
266             "callee FN FileId(0) 0..14 3..9",
267             &[
268                 "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
269                 "test_caller FN FileId(0) 95..149 110..121 : [134..140]",
270             ],
271             &[],
272         );
273     }
274
275     #[test]
276     fn test_call_hierarchy_in_different_files() {
277         check_hierarchy(
278             r#"
279 //- /lib.rs
280 mod foo;
281 use foo::callee;
282
283 fn caller() {
284     call<|>ee();
285 }
286
287 //- /foo/mod.rs
288 pub fn callee() {}
289 "#,
290             "callee FN FileId(1) 0..18 7..13",
291             &["caller FN FileId(0) 27..56 30..36 : [45..51]"],
292             &[],
293         );
294     }
295
296     #[test]
297     fn test_call_hierarchy_outgoing() {
298         check_hierarchy(
299             r#"
300 //- /lib.rs
301 fn callee() {}
302 fn call<|>er() {
303     callee();
304     callee();
305 }
306 "#,
307             "caller FN FileId(0) 15..58 18..24",
308             &[],
309             &["callee FN FileId(0) 0..14 3..9 : [33..39, 47..53]"],
310         );
311     }
312
313     #[test]
314     fn test_call_hierarchy_outgoing_in_different_files() {
315         check_hierarchy(
316             r#"
317 //- /lib.rs
318 mod foo;
319 use foo::callee;
320
321 fn call<|>er() {
322     callee();
323 }
324
325 //- /foo/mod.rs
326 pub fn callee() {}
327 "#,
328             "caller FN FileId(0) 27..56 30..36",
329             &[],
330             &["callee FN FileId(1) 0..18 7..13 : [45..51]"],
331         );
332     }
333
334     #[test]
335     fn test_call_hierarchy_incoming_outgoing() {
336         check_hierarchy(
337             r#"
338 //- /lib.rs
339 fn caller1() {
340     call<|>er2();
341 }
342
343 fn caller2() {
344     caller3();
345 }
346
347 fn caller3() {
348
349 }
350 "#,
351             "caller2 FN FileId(0) 33..64 36..43",
352             &["caller1 FN FileId(0) 0..31 3..10 : [19..26]"],
353             &["caller3 FN FileId(0) 66..83 69..76 : [52..59]"],
354         );
355     }
356
357     #[test]
358     fn test_call_hierarchy_issue_5103() {
359         check_hierarchy(
360             r#"
361 fn a() {
362     b()
363 }
364
365 fn b() {}
366
367 fn main() {
368     a<|>()
369 }
370 "#,
371             "a FN FileId(0) 0..18 3..4",
372             &["main FN FileId(0) 31..52 34..38 : [47..48]"],
373             &["b FN FileId(0) 20..29 23..24 : [13..14]"],
374         );
375
376         check_hierarchy(
377             r#"
378 fn a() {
379     b<|>()
380 }
381
382 fn b() {}
383
384 fn main() {
385     a()
386 }
387 "#,
388             "b FN FileId(0) 20..29 23..24",
389             &["a FN FileId(0) 0..18 3..4 : [13..14]"],
390             &[],
391         );
392     }
393 }