1use std::iter;
4
5use hir::Semantics;
6use ide_db::{
7 FileRange, FxIndexMap, RootDatabase,
8 defs::{Definition, NameClass, NameRefClass},
9 helpers::pick_best_token,
10 search::FileReference,
11};
12use syntax::{AstNode, SyntaxKind::IDENT, ast};
13
14use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav, goto_definition};
15
16#[derive(Debug, Clone)]
17pub struct CallItem {
18 pub target: NavigationTarget,
19 pub ranges: Vec<FileRange>,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct CallHierarchyConfig {
24 pub exclude_tests: bool,
26}
27
28pub(crate) fn call_hierarchy(
29 db: &RootDatabase,
30 position: FilePosition,
31) -> Option<RangeInfo<Vec<NavigationTarget>>> {
32 goto_definition::goto_definition(db, position)
33}
34
35pub(crate) fn incoming_calls(
36 db: &RootDatabase,
37 CallHierarchyConfig { exclude_tests }: CallHierarchyConfig,
38 FilePosition { file_id, offset }: FilePosition,
39) -> Option<Vec<CallItem>> {
40 let sema = &Semantics::new(db);
41
42 let file = sema.parse_guess_edition(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 =
63 references.iter().filter_map(|FileReference { name, .. }| name.as_name_ref());
64 for name in references {
65 let def_nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
67 let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
68 def.try_to_nav(sema.db).map(|nav| (def, nav))
71 });
72
73 if let Some((def, nav)) = def_nav {
74 if exclude_tests && def.is_test(db) {
75 continue;
76 }
77
78 let range = sema.original_range(name.syntax());
79 calls.add(nav.call_site, range.into_file_id(db));
80 if let Some(other) = nav.def_site {
81 calls.add(other, range.into_file_id(db));
82 }
83 }
84 }
85 }
86
87 Some(calls.into_items())
88}
89
90pub(crate) fn outgoing_calls(
91 db: &RootDatabase,
92 CallHierarchyConfig { exclude_tests }: CallHierarchyConfig,
93 FilePosition { file_id, offset }: FilePosition,
94) -> Option<Vec<CallItem>> {
95 let sema = Semantics::new(db);
96 let file = sema.parse_guess_edition(file_id);
97 let file = file.syntax();
98 let token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
99 IDENT => 1,
100 _ => 0,
101 })?;
102 let mut calls = CallLocations::default();
103
104 sema.descend_into_macros_exact(token)
105 .into_iter()
106 .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
107 .filter_map(|item| match item {
108 ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
109 ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
110 ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
111 _ => None,
112 })
113 .flatten()
114 .filter_map(ast::CallableExpr::cast)
115 .filter_map(|call_node| {
116 let (nav_target, range) = match call_node {
117 ast::CallableExpr::Call(call) => {
118 let expr = call.expr()?;
119 let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
120 match callable.kind() {
121 hir::CallableKind::Function(it) => {
122 if exclude_tests && it.is_test(db) {
123 return None;
124 }
125 it.try_to_nav(db)
126 }
127 hir::CallableKind::TupleEnumVariant(it) => it.try_to_nav(db),
128 hir::CallableKind::TupleStruct(it) => it.try_to_nav(db),
129 _ => None,
130 }
131 .zip(Some(sema.original_range(expr.syntax())))
132 }
133 ast::CallableExpr::MethodCall(expr) => {
134 let function = sema.resolve_method_call(&expr)?;
135 if exclude_tests && function.is_test(db) {
136 return None;
137 }
138 function
139 .try_to_nav(db)
140 .zip(Some(sema.original_range(expr.name_ref()?.syntax())))
141 }
142 }?;
143 Some(nav_target.into_iter().zip(iter::repeat(range)))
144 })
145 .flatten()
146 .for_each(|(nav, range)| calls.add(nav, range.into_file_id(db)));
147
148 Some(calls.into_items())
149}
150
151#[derive(Default)]
152struct CallLocations {
153 funcs: FxIndexMap<NavigationTarget, Vec<FileRange>>,
154}
155
156impl CallLocations {
157 fn add(&mut self, target: NavigationTarget, range: FileRange) {
158 self.funcs.entry(target).or_default().push(range);
159 }
160
161 fn into_items(self) -> Vec<CallItem> {
162 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use expect_test::{Expect, expect};
169 use ide_db::FilePosition;
170 use itertools::Itertools;
171
172 use crate::fixture;
173
174 fn check_hierarchy(
175 exclude_tests: bool,
176 #[rust_analyzer::rust_fixture] ra_fixture: &str,
177 expected_nav: Expect,
178 expected_incoming: Expect,
179 expected_outgoing: Expect,
180 ) {
181 fn debug_render(item: crate::CallItem) -> String {
182 format!(
183 "{} : {}",
184 item.target.debug_render(),
185 item.ranges.iter().format_with(", ", |range, f| f(&format_args!(
186 "{:?}:{:?}",
187 range.file_id, range.range
188 )))
189 )
190 }
191
192 let (analysis, pos) = fixture::position(ra_fixture);
193
194 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
195 assert_eq!(navs.len(), 1);
196 let nav = navs.pop().unwrap();
197 expected_nav.assert_eq(&nav.debug_render());
198
199 let config = crate::CallHierarchyConfig { exclude_tests };
200
201 let item_pos =
202 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
203 let incoming_calls = analysis.incoming_calls(config, item_pos).unwrap().unwrap();
204 expected_incoming.assert_eq(&incoming_calls.into_iter().map(debug_render).join("\n"));
205
206 let outgoing_calls = analysis.outgoing_calls(config, item_pos).unwrap().unwrap();
207 expected_outgoing.assert_eq(&outgoing_calls.into_iter().map(debug_render).join("\n"));
208 }
209
210 #[test]
211 fn test_call_hierarchy_on_ref() {
212 check_hierarchy(
213 false,
214 r#"
215//- /lib.rs
216fn callee() {}
217fn caller() {
218 call$0ee();
219}
220"#,
221 expect![["callee Function FileId(0) 0..14 3..9"]],
222 expect!["caller Function FileId(0) 15..44 18..24 : FileId(0):33..39"],
223 expect![[]],
224 );
225 }
226
227 #[test]
228 fn test_call_hierarchy_on_def() {
229 check_hierarchy(
230 false,
231 r#"
232//- /lib.rs
233fn call$0ee() {}
234fn caller() {
235 callee();
236}
237"#,
238 expect![["callee Function FileId(0) 0..14 3..9"]],
239 expect!["caller Function FileId(0) 15..44 18..24 : FileId(0):33..39"],
240 expect![[]],
241 );
242 }
243
244 #[test]
245 fn test_call_hierarchy_in_same_fn() {
246 check_hierarchy(
247 false,
248 r#"
249//- /lib.rs
250fn callee() {}
251fn caller() {
252 call$0ee();
253 callee();
254}
255"#,
256 expect![["callee Function FileId(0) 0..14 3..9"]],
257 expect!["caller Function FileId(0) 15..58 18..24 : FileId(0):33..39, FileId(0):47..53"],
258 expect![[]],
259 );
260 }
261
262 #[test]
263 fn test_call_hierarchy_in_different_fn() {
264 check_hierarchy(
265 false,
266 r#"
267//- /lib.rs
268fn callee() {}
269fn caller1() {
270 call$0ee();
271}
272
273fn caller2() {
274 callee();
275}
276"#,
277 expect![["callee Function FileId(0) 0..14 3..9"]],
278 expect![[r#"
279 caller1 Function FileId(0) 15..45 18..25 : FileId(0):34..40
280 caller2 Function FileId(0) 47..77 50..57 : FileId(0):66..72"#]],
281 expect![[]],
282 );
283 }
284
285 #[test]
286 fn test_call_hierarchy_in_tests_mod() {
287 check_hierarchy(
288 false,
289 r#"
290//- /lib.rs cfg:test
291fn callee() {}
292fn caller1() {
293 call$0ee();
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_caller() {
302 callee();
303 }
304}
305"#,
306 expect![["callee Function FileId(0) 0..14 3..9"]],
307 expect![[r#"
308 caller1 Function FileId(0) 15..45 18..25 : FileId(0):34..40
309 test_caller Function FileId(0) 95..149 110..121 tests : FileId(0):134..140"#]],
310 expect![[]],
311 );
312 }
313
314 #[test]
315 fn test_call_hierarchy_in_different_files() {
316 check_hierarchy(
317 false,
318 r#"
319//- /lib.rs
320mod foo;
321use foo::callee;
322
323fn caller() {
324 call$0ee();
325}
326
327//- /foo/mod.rs
328pub fn callee() {}
329"#,
330 expect!["callee Function FileId(1) 0..18 7..13 foo"],
331 expect!["caller Function FileId(0) 27..56 30..36 : FileId(0):45..51"],
332 expect![[]],
333 );
334 }
335
336 #[test]
337 fn test_call_hierarchy_outgoing() {
338 check_hierarchy(
339 false,
340 r#"
341//- /lib.rs
342fn callee() {}
343fn call$0er() {
344 callee();
345 callee();
346}
347"#,
348 expect![["caller Function FileId(0) 15..58 18..24"]],
349 expect![[]],
350 expect!["callee Function FileId(0) 0..14 3..9 : FileId(0):33..39, FileId(0):47..53"],
351 );
352 }
353
354 #[test]
355 fn test_call_hierarchy_outgoing_in_different_files() {
356 check_hierarchy(
357 false,
358 r#"
359//- /lib.rs
360mod foo;
361use foo::callee;
362
363fn call$0er() {
364 callee();
365}
366
367//- /foo/mod.rs
368pub fn callee() {}
369"#,
370 expect![["caller Function FileId(0) 27..56 30..36"]],
371 expect![[]],
372 expect!["callee Function FileId(1) 0..18 7..13 foo : FileId(0):45..51"],
373 );
374 }
375
376 #[test]
377 fn test_call_hierarchy_incoming_outgoing() {
378 check_hierarchy(
379 false,
380 r#"
381//- /lib.rs
382fn caller1() {
383 call$0er2();
384}
385
386fn caller2() {
387 caller3();
388}
389
390fn caller3() {
391
392}
393"#,
394 expect![["caller2 Function FileId(0) 33..64 36..43"]],
395 expect!["caller1 Function FileId(0) 0..31 3..10 : FileId(0):19..26"],
396 expect!["caller3 Function FileId(0) 66..83 69..76 : FileId(0):52..59"],
397 );
398 }
399
400 #[test]
401 fn test_call_hierarchy_issue_5103() {
402 check_hierarchy(
403 false,
404 r#"
405fn a() {
406 b()
407}
408
409fn b() {}
410
411fn main() {
412 a$0()
413}
414"#,
415 expect![["a Function FileId(0) 0..18 3..4"]],
416 expect!["main Function FileId(0) 31..52 34..38 : FileId(0):47..48"],
417 expect!["b Function FileId(0) 20..29 23..24 : FileId(0):13..14"],
418 );
419
420 check_hierarchy(
421 false,
422 r#"
423fn a() {
424 b$0()
425}
426
427fn b() {}
428
429fn main() {
430 a()
431}
432"#,
433 expect![["b Function FileId(0) 20..29 23..24"]],
434 expect!["a Function FileId(0) 0..18 3..4 : FileId(0):13..14"],
435 expect![[]],
436 );
437 }
438
439 #[test]
440 fn test_call_hierarchy_in_macros_incoming() {
441 check_hierarchy(
442 false,
443 r#"
444macro_rules! define {
445 ($ident:ident) => {
446 fn $ident {}
447 }
448}
449macro_rules! call {
450 ($ident:ident) => {
451 $ident()
452 }
453}
454define!(callee)
455fn caller() {
456 call!(call$0ee);
457}
458"#,
459 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
460 expect!["caller Function FileId(0) 160..194 163..169 : FileId(0):184..190"],
461 expect![[]],
462 );
463 check_hierarchy(
464 false,
465 r#"
466macro_rules! define {
467 ($ident:ident) => {
468 fn $ident {}
469 }
470}
471macro_rules! call {
472 ($ident:ident) => {
473 $ident()
474 }
475}
476define!(cal$0lee)
477fn caller() {
478 call!(callee);
479}
480"#,
481 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
482 expect!["caller Function FileId(0) 160..194 163..169 : FileId(0):184..190"],
483 expect![[]],
484 );
485 }
486
487 #[test]
488 fn test_call_hierarchy_in_macros_outgoing() {
489 check_hierarchy(
490 false,
491 r#"
492macro_rules! define {
493 ($ident:ident) => {
494 fn $ident {}
495 }
496}
497macro_rules! call {
498 ($ident:ident) => {
499 $ident()
500 }
501}
502define!(callee)
503fn caller$0() {
504 call!(callee);
505}
506"#,
507 expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
508 expect![[]],
509 expect![[]],
511 );
512 }
513
514 #[test]
515 fn test_call_hierarchy_in_macros_incoming_different_files() {
516 check_hierarchy(
517 false,
518 r#"
519//- /lib.rs
520#[macro_use]
521mod foo;
522define!(callee)
523fn caller() {
524 call!(call$0ee);
525}
526//- /foo.rs
527macro_rules! define {
528 ($ident:ident) => {
529 fn $ident {}
530 }
531}
532macro_rules! call {
533 ($ident:ident) => {
534 $ident()
535 }
536}
537"#,
538 expect!["callee Function FileId(0) 22..37 30..36"],
539 expect!["caller Function FileId(0) 38..72 41..47 : FileId(0):62..68"],
540 expect![[]],
541 );
542 check_hierarchy(
543 false,
544 r#"
545//- /lib.rs
546#[macro_use]
547mod foo;
548define!(cal$0lee)
549fn caller() {
550 call!(callee);
551}
552//- /foo.rs
553macro_rules! define {
554 ($ident:ident) => {
555 fn $ident {}
556 }
557}
558macro_rules! call {
559 ($ident:ident) => {
560 $ident()
561 }
562}
563"#,
564 expect!["callee Function FileId(0) 22..37 30..36"],
565 expect!["caller Function FileId(0) 38..72 41..47 : FileId(0):62..68"],
566 expect![[]],
567 );
568 check_hierarchy(
569 false,
570 r#"
571//- /lib.rs
572#[macro_use]
573mod foo;
574define!(cal$0lee)
575call!(callee);
576//- /foo.rs
577macro_rules! define {
578 ($ident:ident) => {
579 fn $ident {}
580 }
581}
582macro_rules! call {
583 ($ident:ident) => {
584 fn caller() {
585 $ident()
586 }
587 fn $ident() {
588 $ident()
589 }
590 }
591}
592"#,
593 expect!["callee Function FileId(0) 22..37 30..36"],
594 expect![[r#"
595 caller Function FileId(0) 38..43 : FileId(0):44..50
596 caller Function FileId(1) 130..136 130..136 : FileId(0):44..50
597 callee Function FileId(0) 38..52 44..50 : FileId(0):44..50"#]],
598 expect![[]],
599 );
600 }
601
602 #[test]
603 fn test_call_hierarchy_in_macros_outgoing_different_files() {
604 check_hierarchy(
605 false,
606 r#"
607//- /lib.rs
608#[macro_use]
609mod foo;
610define!(callee)
611fn caller$0() {
612 call!(callee);
613}
614//- /foo.rs
615macro_rules! define {
616 ($ident:ident) => {
617 fn $ident {}
618 }
619}
620macro_rules! call {
621 ($ident:ident) => {
622 $ident()
623 callee()
624 }
625}
626"#,
627 expect!["caller Function FileId(0) 38..72 41..47"],
628 expect![[]],
629 expect![[]],
631 );
632 check_hierarchy(
633 false,
634 r#"
635//- /lib.rs
636#[macro_use]
637mod foo;
638define!(callee)
639fn caller$0() {
640 call!(callee);
641}
642//- /foo.rs
643macro_rules! define {
644 () => {
645 fn callee {}
646 }
647}
648macro_rules! call {
649 ($ident:ident) => {
650 $ident()
651 callee()
652 }
653}
654"#,
655 expect!["caller Function FileId(0) 38..72 41..47"],
656 expect![[]],
657 expect![[]],
659 );
660 }
661
662 #[test]
663 fn test_trait_method_call_hierarchy() {
664 check_hierarchy(
665 false,
666 r#"
667trait T1 {
668 fn call$0ee();
669}
670
671struct S1;
672
673impl T1 for S1 {
674 fn callee() {}
675}
676
677fn caller() {
678 S1::callee();
679}
680"#,
681 expect!["callee Function FileId(0) 15..27 18..24 T1"],
682 expect!["caller Function FileId(0) 82..115 85..91 : FileId(0):104..110"],
683 expect![[]],
684 );
685 }
686
687 #[test]
688 fn test_call_hierarchy_excluding_tests() {
689 check_hierarchy(
690 false,
691 r#"
692fn main() {
693 f1();
694}
695
696fn f1$0() {
697 f2(); f3();
698}
699
700fn f2() {
701 f1(); f3();
702}
703
704#[test]
705fn f3() {
706 f1(); f2();
707}
708"#,
709 expect!["f1 Function FileId(0) 25..52 28..30"],
710 expect![[r#"
711 main Function FileId(0) 0..23 3..7 : FileId(0):16..18
712 f2 Function FileId(0) 54..81 57..59 : FileId(0):68..70
713 f3 Function FileId(0) 83..118 94..96 : FileId(0):105..107"#]],
714 expect![[r#"
715 f2 Function FileId(0) 54..81 57..59 : FileId(0):39..41
716 f3 Function FileId(0) 83..118 94..96 : FileId(0):45..47"#]],
717 );
718
719 check_hierarchy(
720 true,
721 r#"
722fn main() {
723 f1();
724}
725
726fn f1$0() {
727 f2(); f3();
728}
729
730fn f2() {
731 f1(); f3();
732}
733
734#[test]
735fn f3() {
736 f1(); f2();
737}
738"#,
739 expect!["f1 Function FileId(0) 25..52 28..30"],
740 expect![[r#"
741 main Function FileId(0) 0..23 3..7 : FileId(0):16..18
742 f2 Function FileId(0) 54..81 57..59 : FileId(0):68..70"#]],
743 expect!["f2 Function FileId(0) 54..81 57..59 : FileId(0):39..41"],
744 );
745 }
746}