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