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