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