1 //! Entry point for call-hierarchy
5 defs::{Definition, NameClass, NameRefClass},
6 helpers::pick_best_token,
8 FxIndexMap, RootDatabase,
10 use syntax::{ast, AstNode, SyntaxKind::NAME, TextRange};
12 use crate::{goto_definition, FilePosition, NavigationTarget, RangeInfo, TryToNav};
14 #[derive(Debug, Clone)]
16 pub target: NavigationTarget,
17 pub ranges: Vec<TextRange>,
22 pub(crate) fn debug_render(&self) -> String {
23 format!("{} : {:?}", self.target.debug_render(), self.ranges)
27 pub(crate) fn call_hierarchy(
29 position: FilePosition,
30 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
31 goto_definition::goto_definition(db, position)
34 pub(crate) fn incoming_calls(
36 FilePosition { file_id, offset }: FilePosition,
37 ) -> Option<Vec<CallItem>> {
38 let sema = &Semantics::new(db);
40 let file = sema.parse(file_id);
41 let file = file.syntax();
42 let mut calls = CallLocations::default();
45 .find_nodes_at_offset_with_descend(file, offset)
46 .filter_map(move |node| match node {
47 ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
48 NameRefClass::Definition(def @ Definition::Function(_)) => Some(def),
51 ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
52 NameClass::Definition(def @ Definition::Function(_)) => Some(def),
55 ast::NameLike::Lifetime(_) => None,
57 .flat_map(|func| func.usages(sema).all());
59 for (_, references) in references {
60 let references = references.into_iter().map(|FileReference { name, .. }| name);
61 for name in references {
62 // This target is the containing function
63 let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
64 let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
65 def.try_to_nav(sema.db)
67 if let Some(nav) = nav {
68 calls.add(nav, sema.original_range(name.syntax()).range);
73 Some(calls.into_items())
76 pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
77 let sema = Semantics::new(db);
78 let file_id = position.file_id;
79 let file = sema.parse(file_id);
80 let file = file.syntax();
81 let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
85 let mut calls = CallLocations::default();
87 sema.descend_into_macros(token)
89 .filter_map(|it| it.ancestors().nth(1).and_then(ast::Item::cast))
90 .filter_map(|item| match item {
91 ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
92 ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
93 ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
97 .filter_map(ast::CallableExpr::cast)
98 .filter_map(|call_node| {
99 let (nav_target, range) = match call_node {
100 ast::CallableExpr::Call(call) => {
101 let expr = call.expr()?;
102 let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
103 match callable.kind() {
104 hir::CallableKind::Function(it) => {
105 let range = expr.syntax().text_range();
106 it.try_to_nav(db).zip(Some(range))
111 ast::CallableExpr::MethodCall(expr) => {
112 let range = expr.name_ref()?.syntax().text_range();
113 let function = sema.resolve_method_call(&expr)?;
114 function.try_to_nav(db).zip(Some(range))
117 Some((nav_target, range))
119 .for_each(|(nav, range)| calls.add(nav, range));
121 Some(calls.into_items())
125 struct CallLocations {
126 funcs: FxIndexMap<NavigationTarget, Vec<TextRange>>,
130 fn add(&mut self, target: NavigationTarget, range: TextRange) {
131 self.funcs.entry(target).or_default().push(range);
134 fn into_items(self) -> Vec<CallItem> {
135 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
141 use expect_test::{expect, Expect};
142 use ide_db::base_db::FilePosition;
143 use itertools::Itertools;
150 expected_incoming: Expect,
151 expected_outgoing: Expect,
153 let (analysis, pos) = fixture::position(ra_fixture);
155 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
156 assert_eq!(navs.len(), 1);
157 let nav = navs.pop().unwrap();
158 expected.assert_eq(&nav.debug_render());
161 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
162 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
164 .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
166 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
168 .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
172 fn test_call_hierarchy_on_ref() {
181 expect![["callee Function FileId(0) 0..14 3..9"]],
182 expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
188 fn test_call_hierarchy_on_def() {
197 expect![["callee Function FileId(0) 0..14 3..9"]],
198 expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
204 fn test_call_hierarchy_in_same_fn() {
214 expect![["callee Function FileId(0) 0..14 3..9"]],
215 expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
221 fn test_call_hierarchy_in_different_fn() {
234 expect![["callee Function FileId(0) 0..14 3..9"]],
236 caller1 Function FileId(0) 15..45 18..25 : [34..40]
237 caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
243 fn test_call_hierarchy_in_tests_mod() {
262 expect![["callee Function FileId(0) 0..14 3..9"]],
264 caller1 Function FileId(0) 15..45 18..25 : [34..40]
265 test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
271 fn test_call_hierarchy_in_different_files() {
285 expect![["callee Function FileId(1) 0..18 7..13"]],
286 expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
292 fn test_call_hierarchy_outgoing() {
302 expect![["caller Function FileId(0) 15..58 18..24"]],
304 expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
309 fn test_call_hierarchy_outgoing_in_different_files() {
323 expect![["caller Function FileId(0) 27..56 30..36"]],
325 expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
330 fn test_call_hierarchy_incoming_outgoing() {
346 expect![["caller2 Function FileId(0) 33..64 36..43"]],
347 expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
348 expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
353 fn test_call_hierarchy_issue_5103() {
366 expect![["a Function FileId(0) 0..18 3..4"]],
367 expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
368 expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
383 expect![["b Function FileId(0) 20..29 23..24"]],
384 expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
390 fn test_call_hierarchy_in_macros_incoming() {
393 macro_rules! define {
408 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
409 expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
414 macro_rules! define {
429 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
430 expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
436 fn test_call_hierarchy_in_macros_outgoing() {
439 macro_rules! define {
454 expect![[r#"caller Function FileId(0) 160..194 163..169"#]],