ide/
call_hierarchy.rs

1//! Entry point for call-hierarchy
2
3use 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    /// Whether to exclude tests from the call hierarchy
27    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            // This target is the containing function
74            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                // We should return def before check if it is a test, so that we
77                // will not continue to search for outer fn in nested fns
78                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            // FIXME
517            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            // FIXME
637            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            // FIXME
665            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}