]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/call_hierarchy.rs
23bffd564899dad3caff71f13743c074b0a24c4b
[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::{
7     defs::{Definition, NameClass, NameRefClass},
8     helpers::pick_best_token,
9     search::FileReference,
10     RootDatabase,
11 };
12 use syntax::{ast, AstNode, SyntaxKind::NAME, TextRange};
13
14 use crate::{display::TryToNav, goto_definition, FilePosition, NavigationTarget, RangeInfo};
15
16 #[derive(Debug, Clone)]
17 pub struct CallItem {
18     pub target: NavigationTarget,
19     pub ranges: Vec<TextRange>,
20 }
21
22 impl CallItem {
23     #[cfg(test)]
24     pub(crate) fn debug_render(&self) -> String {
25         format!("{} : {:?}", self.target.debug_render(), self.ranges)
26     }
27 }
28
29 pub(crate) fn call_hierarchy(
30     db: &RootDatabase,
31     position: FilePosition,
32 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
33     goto_definition::goto_definition(db, position)
34 }
35
36 pub(crate) fn incoming_calls(
37     db: &RootDatabase,
38     FilePosition { file_id, offset }: FilePosition,
39 ) -> Option<Vec<CallItem>> {
40     let sema = &Semantics::new(db);
41
42     let file = sema.parse(file_id);
43     let file = file.syntax();
44     let mut calls = CallLocations::default();
45
46     let references = sema
47         .find_nodes_at_offset_with_descend(file, offset)
48         .filter_map(move |node| match node {
49             ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
50                 NameRefClass::Definition(def @ Definition::Function(_)) => Some(def),
51                 _ => None,
52             },
53             ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
54                 NameClass::Definition(def @ Definition::Function(_)) => Some(def),
55                 _ => None,
56             },
57             ast::NameLike::Lifetime(_) => None,
58         })
59         .flat_map(|func| func.usages(sema).all());
60
61     for (_, references) in references {
62         let references = references.into_iter().map(|FileReference { name, .. }| name);
63         for name in references {
64             // This target is the containing function
65             let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
66                 let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
67                 def.try_to_nav(sema.db)
68             });
69             if let Some(nav) = nav {
70                 calls.add(nav, sema.original_range(name.syntax()).range);
71             }
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 = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
84         NAME => 1,
85         _ => 0,
86     })?;
87     let mut calls = CallLocations::default();
88
89     sema.descend_into_macros(token)
90         .into_iter()
91         .filter_map(|it| it.ancestors().nth(1).and_then(ast::Item::cast))
92         .filter_map(|item| match item {
93             ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
94             ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
95             ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
96             _ => None,
97         })
98         .flatten()
99         .filter_map(ast::CallableExpr::cast)
100         .filter_map(|call_node| {
101             let (nav_target, range) = match call_node {
102                 ast::CallableExpr::Call(call) => {
103                     let expr = call.expr()?;
104                     let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
105                     match callable.kind() {
106                         hir::CallableKind::Function(it) => {
107                             let range = expr.syntax().text_range();
108                             it.try_to_nav(db).zip(Some(range))
109                         }
110                         _ => None,
111                     }
112                 }
113                 ast::CallableExpr::MethodCall(expr) => {
114                     let range = expr.name_ref()?.syntax().text_range();
115                     let function = sema.resolve_method_call(&expr)?;
116                     function.try_to_nav(db).zip(Some(range))
117                 }
118             }?;
119             Some((nav_target, range))
120         })
121         .for_each(|(nav, range)| calls.add(nav, range));
122
123     Some(calls.into_items())
124 }
125
126 #[derive(Default)]
127 struct CallLocations {
128     funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
129 }
130
131 impl CallLocations {
132     fn add(&mut self, target: NavigationTarget, range: TextRange) {
133         self.funcs.entry(target).or_default().push(range);
134     }
135
136     fn into_items(self) -> Vec<CallItem> {
137         self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
138     }
139 }
140
141 #[cfg(test)]
142 mod tests {
143     use expect_test::{expect, Expect};
144     use ide_db::base_db::FilePosition;
145     use itertools::Itertools;
146
147     use crate::fixture;
148
149     fn check_hierarchy(
150         ra_fixture: &str,
151         expected: Expect,
152         expected_incoming: Expect,
153         expected_outgoing: Expect,
154     ) {
155         let (analysis, pos) = fixture::position(ra_fixture);
156
157         let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
158         assert_eq!(navs.len(), 1);
159         let nav = navs.pop().unwrap();
160         expected.assert_eq(&nav.debug_render());
161
162         let item_pos =
163             FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
164         let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
165         expected_incoming
166             .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
167
168         let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
169         expected_outgoing
170             .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
171     }
172
173     #[test]
174     fn test_call_hierarchy_on_ref() {
175         check_hierarchy(
176             r#"
177 //- /lib.rs
178 fn callee() {}
179 fn caller() {
180     call$0ee();
181 }
182 "#,
183             expect![["callee Function FileId(0) 0..14 3..9"]],
184             expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
185             expect![[]],
186         );
187     }
188
189     #[test]
190     fn test_call_hierarchy_on_def() {
191         check_hierarchy(
192             r#"
193 //- /lib.rs
194 fn call$0ee() {}
195 fn caller() {
196     callee();
197 }
198 "#,
199             expect![["callee Function FileId(0) 0..14 3..9"]],
200             expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
201             expect![[]],
202         );
203     }
204
205     #[test]
206     fn test_call_hierarchy_in_same_fn() {
207         check_hierarchy(
208             r#"
209 //- /lib.rs
210 fn callee() {}
211 fn caller() {
212     call$0ee();
213     callee();
214 }
215 "#,
216             expect![["callee Function FileId(0) 0..14 3..9"]],
217             expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
218             expect![[]],
219         );
220     }
221
222     #[test]
223     fn test_call_hierarchy_in_different_fn() {
224         check_hierarchy(
225             r#"
226 //- /lib.rs
227 fn callee() {}
228 fn caller1() {
229     call$0ee();
230 }
231
232 fn caller2() {
233     callee();
234 }
235 "#,
236             expect![["callee Function FileId(0) 0..14 3..9"]],
237             expect![["
238                 caller1 Function FileId(0) 15..45 18..25 : [34..40]
239                 caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
240             expect![[]],
241         );
242     }
243
244     #[test]
245     fn test_call_hierarchy_in_tests_mod() {
246         check_hierarchy(
247             r#"
248 //- /lib.rs cfg:test
249 fn callee() {}
250 fn caller1() {
251     call$0ee();
252 }
253
254 #[cfg(test)]
255 mod tests {
256     use super::*;
257
258     #[test]
259     fn test_caller() {
260         callee();
261     }
262 }
263 "#,
264             expect![["callee Function FileId(0) 0..14 3..9"]],
265             expect![[r#"
266                 caller1 Function FileId(0) 15..45 18..25 : [34..40]
267                 test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
268             expect![[]],
269         );
270     }
271
272     #[test]
273     fn test_call_hierarchy_in_different_files() {
274         check_hierarchy(
275             r#"
276 //- /lib.rs
277 mod foo;
278 use foo::callee;
279
280 fn caller() {
281     call$0ee();
282 }
283
284 //- /foo/mod.rs
285 pub fn callee() {}
286 "#,
287             expect![["callee Function FileId(1) 0..18 7..13"]],
288             expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
289             expect![[]],
290         );
291     }
292
293     #[test]
294     fn test_call_hierarchy_outgoing() {
295         check_hierarchy(
296             r#"
297 //- /lib.rs
298 fn callee() {}
299 fn call$0er() {
300     callee();
301     callee();
302 }
303 "#,
304             expect![["caller Function FileId(0) 15..58 18..24"]],
305             expect![[]],
306             expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
307         );
308     }
309
310     #[test]
311     fn test_call_hierarchy_outgoing_in_different_files() {
312         check_hierarchy(
313             r#"
314 //- /lib.rs
315 mod foo;
316 use foo::callee;
317
318 fn call$0er() {
319     callee();
320 }
321
322 //- /foo/mod.rs
323 pub fn callee() {}
324 "#,
325             expect![["caller Function FileId(0) 27..56 30..36"]],
326             expect![[]],
327             expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
328         );
329     }
330
331     #[test]
332     fn test_call_hierarchy_incoming_outgoing() {
333         check_hierarchy(
334             r#"
335 //- /lib.rs
336 fn caller1() {
337     call$0er2();
338 }
339
340 fn caller2() {
341     caller3();
342 }
343
344 fn caller3() {
345
346 }
347 "#,
348             expect![["caller2 Function FileId(0) 33..64 36..43"]],
349             expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
350             expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
351         );
352     }
353
354     #[test]
355     fn test_call_hierarchy_issue_5103() {
356         check_hierarchy(
357             r#"
358 fn a() {
359     b()
360 }
361
362 fn b() {}
363
364 fn main() {
365     a$0()
366 }
367 "#,
368             expect![["a Function FileId(0) 0..18 3..4"]],
369             expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
370             expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
371         );
372
373         check_hierarchy(
374             r#"
375 fn a() {
376     b$0()
377 }
378
379 fn b() {}
380
381 fn main() {
382     a()
383 }
384 "#,
385             expect![["b Function FileId(0) 20..29 23..24"]],
386             expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
387             expect![[]],
388         );
389     }
390
391     #[test]
392     fn test_call_hierarchy_in_macros_incoming() {
393         check_hierarchy(
394             r#"
395 macro_rules! define {
396     ($ident:ident) => {
397         fn $ident {}
398     }
399 }
400 macro_rules! call {
401     ($ident:ident) => {
402         $ident()
403     }
404 }
405 define!(callee)
406 fn caller() {
407     call!(call$0ee);
408 }
409 "#,
410             expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
411             expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
412             expect![[]],
413         );
414         check_hierarchy(
415             r#"
416 macro_rules! define {
417     ($ident:ident) => {
418         fn $ident {}
419     }
420 }
421 macro_rules! call {
422     ($ident:ident) => {
423         $ident()
424     }
425 }
426 define!(cal$0lee)
427 fn caller() {
428     call!(callee);
429 }
430 "#,
431             expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
432             expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
433             expect![[]],
434         );
435     }
436
437     #[test]
438     fn test_call_hierarchy_in_macros_outgoing() {
439         check_hierarchy(
440             r#"
441 macro_rules! define {
442     ($ident:ident) => {
443         fn $ident {}
444     }
445 }
446 macro_rules! call {
447     ($ident:ident) => {
448         $ident()
449     }
450 }
451 define!(callee)
452 fn caller$0() {
453     call!(callee);
454 }
455 "#,
456             expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
457             expect![[]],
458             // FIXME
459             expect![[]],
460         );
461     }
462 }