1 //! Entry point for call-hierarchy
3 use indexmap::IndexMap;
6 use ide_db::call_info::FnCallNode;
7 use ide_db::RootDatabase;
8 use syntax::{ast, AstNode, TextRange};
11 display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
14 #[derive(Debug, Clone)]
16 pub target: NavigationTarget,
17 pub ranges: Vec<TextRange>,
22 pub(crate) fn assert_match(&self, expected: &str) {
23 let actual = self.debug_render();
24 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
28 pub(crate) fn debug_render(&self) -> String {
29 format!("{} : {:?}", self.target.debug_render(), self.ranges)
33 pub(crate) fn call_hierarchy(
35 position: FilePosition,
36 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
37 goto_definition::goto_definition(db, position)
40 pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
41 let sema = Semantics::new(db);
44 // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
45 // 3. Add ranges relative to the start of the fndef.
46 let refs = references::find_all_refs(&sema, position, None)?;
48 let mut calls = CallLocations::default();
50 for (file_id, references) in refs.references {
51 let file = sema.parse(file_id);
52 let file = file.syntax();
53 for (r_range, _) in references {
54 let token = file.token_at_offset(r_range.start()).next()?;
55 let token = sema.descend_into_macros(token);
56 let syntax = token.parent();
58 // This target is the containing function
59 if let Some(nav) = syntax.ancestors().find_map(|node| {
60 let fn_ = ast::Fn::cast(node)?;
61 let def = sema.to_def(&fn_)?;
62 def.try_to_nav(sema.db)
64 let relative_range = r_range;
65 calls.add(&nav, relative_range);
70 Some(calls.into_items())
73 pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
74 let sema = Semantics::new(db);
75 let file_id = position.file_id;
76 let file = sema.parse(file_id);
77 let file = file.syntax();
78 let token = file.token_at_offset(position.offset).next()?;
79 let token = sema.descend_into_macros(token);
80 let syntax = token.parent();
82 let mut calls = CallLocations::default();
86 .filter_map(|node| FnCallNode::with_node_exact(&node))
87 .filter_map(|call_node| {
88 let name_ref = call_node.name_ref()?;
89 let func_target = match call_node {
90 FnCallNode::CallExpr(expr) => {
91 //FIXME: Type::as_callable is broken
92 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
93 match callable.kind() {
94 hir::CallableKind::Function(it) => it.try_to_nav(db),
98 FnCallNode::MethodCallExpr(expr) => {
99 let function = sema.resolve_method_call(&expr)?;
100 function.try_to_nav(db)
103 Some((func_target, name_ref.syntax().text_range()))
105 .for_each(|(nav, range)| calls.add(&nav, range));
107 Some(calls.into_items())
111 struct CallLocations {
112 funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
116 fn add(&mut self, target: &NavigationTarget, range: TextRange) {
117 self.funcs.entry(target.clone()).or_default().push(range);
120 fn into_items(self) -> Vec<CallItem> {
121 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
127 use ide_db::base_db::FilePosition;
134 expected_incoming: &[&str],
135 expected_outgoing: &[&str],
137 let (analysis, pos) = fixture::position(ra_fixture);
139 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
140 assert_eq!(navs.len(), 1);
141 let nav = navs.pop().unwrap();
142 nav.assert_match(expected);
145 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
146 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
147 assert_eq!(incoming_calls.len(), expected_incoming.len());
149 for call in 0..incoming_calls.len() {
150 incoming_calls[call].assert_match(expected_incoming[call]);
153 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
154 assert_eq!(outgoing_calls.len(), expected_outgoing.len());
156 for call in 0..outgoing_calls.len() {
157 outgoing_calls[call].assert_match(expected_outgoing[call]);
162 fn test_call_hierarchy_on_ref() {
171 "callee Function FileId(0) 0..14 3..9",
172 &["caller Function FileId(0) 15..44 18..24 : [33..39]"],
178 fn test_call_hierarchy_on_def() {
187 "callee Function FileId(0) 0..14 3..9",
188 &["caller Function FileId(0) 15..44 18..24 : [33..39]"],
194 fn test_call_hierarchy_in_same_fn() {
204 "callee Function FileId(0) 0..14 3..9",
205 &["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"],
211 fn test_call_hierarchy_in_different_fn() {
224 "callee Function FileId(0) 0..14 3..9",
226 "caller1 Function FileId(0) 15..45 18..25 : [34..40]",
227 "caller2 Function FileId(0) 47..77 50..57 : [66..72]",
234 fn test_call_hierarchy_in_tests_mod() {
253 "callee Function FileId(0) 0..14 3..9",
255 "caller1 Function FileId(0) 15..45 18..25 : [34..40]",
256 "test_caller Function FileId(0) 95..149 110..121 : [134..140]",
263 fn test_call_hierarchy_in_different_files() {
277 "callee Function FileId(1) 0..18 7..13",
278 &["caller Function FileId(0) 27..56 30..36 : [45..51]"],
284 fn test_call_hierarchy_outgoing() {
294 "caller Function FileId(0) 15..58 18..24",
296 &["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"],
301 fn test_call_hierarchy_outgoing_in_different_files() {
315 "caller Function FileId(0) 27..56 30..36",
317 &["callee Function FileId(1) 0..18 7..13 : [45..51]"],
322 fn test_call_hierarchy_incoming_outgoing() {
338 "caller2 Function FileId(0) 33..64 36..43",
339 &["caller1 Function FileId(0) 0..31 3..10 : [19..26]"],
340 &["caller3 Function FileId(0) 66..83 69..76 : [52..59]"],
345 fn test_call_hierarchy_issue_5103() {
358 "a Function FileId(0) 0..18 3..4",
359 &["main Function FileId(0) 31..52 34..38 : [47..48]"],
360 &["b Function FileId(0) 20..29 23..24 : [13..14]"],
375 "b Function FileId(0) 20..29 23..24",
376 &["a Function FileId(0) 0..18 3..4 : [13..14]"],