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    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    /// Whether to exclude tests from the call hierarchy
25    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            // This target is the containing function
66            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                // We should return def before check if it is a test, so that we
69                // will not continue to search for outer fn in nested fns
70                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            // FIXME
510            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            // FIXME
630            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            // FIXME
658            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}