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