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