1 //! Entry point for call-hierarchy
3 use indexmap::IndexMap;
7 defs::{Definition, NameClass, NameRefClass},
8 helpers::pick_best_token,
12 use syntax::{ast, AstNode, SyntaxKind::NAME, TextRange};
14 use crate::{display::TryToNav, goto_definition, FilePosition, NavigationTarget, RangeInfo};
16 #[derive(Debug, Clone)]
18 pub target: NavigationTarget,
19 pub ranges: Vec<TextRange>,
24 pub(crate) fn debug_render(&self) -> String {
25 format!("{} : {:?}", self.target.debug_render(), self.ranges)
29 pub(crate) fn call_hierarchy(
31 position: FilePosition,
32 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
33 goto_definition::goto_definition(db, position)
36 pub(crate) fn incoming_calls(
38 FilePosition { file_id, offset }: FilePosition,
39 ) -> Option<Vec<CallItem>> {
40 let sema = &Semantics::new(db);
42 let file = sema.parse(file_id);
43 let file = file.syntax();
44 let mut calls = CallLocations::default();
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(
51 def @ Definition::ModuleDef(hir::ModuleDef::Function(_)),
55 ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
56 NameClass::Definition(def @ Definition::ModuleDef(hir::ModuleDef::Function(_))) => {
61 ast::NameLike::Lifetime(_) => None,
63 .flat_map(|func| func.usages(sema).all());
65 for (_, references) in references {
66 let references = references.into_iter().map(|FileReference { name, .. }| name);
67 for name in references {
68 // This target is the containing function
69 let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
70 let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
71 def.try_to_nav(sema.db)
73 if let Some(nav) = nav {
74 calls.add(nav, sema.original_range(name.syntax()).range);
79 Some(calls.into_items())
82 pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
83 let sema = Semantics::new(db);
84 let file_id = position.file_id;
85 let file = sema.parse(file_id);
86 let file = file.syntax();
87 let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
91 let mut calls = CallLocations::default();
93 sema.descend_into_macros(token)
95 .filter_map(|it| it.ancestors().nth(1).and_then(ast::Item::cast))
96 .filter_map(|item| match item {
97 ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
98 ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
99 ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
103 .filter_map(ast::CallableExpr::cast)
104 .filter_map(|call_node| {
105 let (nav_target, range) = match call_node {
106 ast::CallableExpr::Call(call) => {
107 let expr = call.expr()?;
108 let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
109 match callable.kind() {
110 hir::CallableKind::Function(it) => {
111 let range = expr.syntax().text_range();
112 it.try_to_nav(db).zip(Some(range))
117 ast::CallableExpr::MethodCall(expr) => {
118 let range = expr.name_ref()?.syntax().text_range();
119 let function = sema.resolve_method_call(&expr)?;
120 function.try_to_nav(db).zip(Some(range))
123 Some((nav_target, range))
125 .for_each(|(nav, range)| calls.add(nav, range));
127 Some(calls.into_items())
131 struct CallLocations {
132 funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
136 fn add(&mut self, target: NavigationTarget, range: TextRange) {
137 self.funcs.entry(target).or_default().push(range);
140 fn into_items(self) -> Vec<CallItem> {
141 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
147 use expect_test::{expect, Expect};
148 use ide_db::base_db::FilePosition;
149 use itertools::Itertools;
156 expected_incoming: Expect,
157 expected_outgoing: Expect,
159 let (analysis, pos) = fixture::position(ra_fixture);
161 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
162 assert_eq!(navs.len(), 1);
163 let nav = navs.pop().unwrap();
164 expected.assert_eq(&nav.debug_render());
167 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
168 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
170 .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
172 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
174 .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
178 fn test_call_hierarchy_on_ref() {
187 expect![["callee Function FileId(0) 0..14 3..9"]],
188 expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
194 fn test_call_hierarchy_on_def() {
203 expect![["callee Function FileId(0) 0..14 3..9"]],
204 expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
210 fn test_call_hierarchy_in_same_fn() {
220 expect![["callee Function FileId(0) 0..14 3..9"]],
221 expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
227 fn test_call_hierarchy_in_different_fn() {
240 expect![["callee Function FileId(0) 0..14 3..9"]],
242 caller1 Function FileId(0) 15..45 18..25 : [34..40]
243 caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
249 fn test_call_hierarchy_in_tests_mod() {
268 expect![["callee Function FileId(0) 0..14 3..9"]],
270 caller1 Function FileId(0) 15..45 18..25 : [34..40]
271 test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
277 fn test_call_hierarchy_in_different_files() {
291 expect![["callee Function FileId(1) 0..18 7..13"]],
292 expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
298 fn test_call_hierarchy_outgoing() {
308 expect![["caller Function FileId(0) 15..58 18..24"]],
310 expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
315 fn test_call_hierarchy_outgoing_in_different_files() {
329 expect![["caller Function FileId(0) 27..56 30..36"]],
331 expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
336 fn test_call_hierarchy_incoming_outgoing() {
352 expect![["caller2 Function FileId(0) 33..64 36..43"]],
353 expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
354 expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
359 fn test_call_hierarchy_issue_5103() {
372 expect![["a Function FileId(0) 0..18 3..4"]],
373 expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
374 expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
389 expect![["b Function FileId(0) 20..29 23..24"]],
390 expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
396 fn test_call_hierarchy_in_macros_incoming() {
399 macro_rules! define {
414 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
415 expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
420 macro_rules! define {
435 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
436 expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
442 fn test_call_hierarchy_in_macros_outgoing() {
445 macro_rules! define {
460 expect![[r#"caller Function FileId(0) 160..194 163..169"#]],