ide/
call_hierarchy.rs

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