1 //! Entry point for call-hierarchy
3 use indexmap::IndexMap;
6 use ide_db::RootDatabase;
7 use syntax::{ast, match_ast, AstNode, TextRange};
10 call_info::FnCallNode, display::ToNav, goto_definition, references, FilePosition,
11 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 reference in refs.info.references() {
51 let file_id = reference.file_range.file_id;
52 let file = sema.parse(file_id);
53 let file = file.syntax();
54 let token = file.token_at_offset(reference.file_range.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| {
63 let def = sema.to_def(&it)?;
64 Some(def.to_nav(sema.db))
70 let relative_range = reference.file_range.range;
71 calls.add(&nav, relative_range);
75 Some(calls.into_items())
78 pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
79 let sema = Semantics::new(db);
80 let file_id = position.file_id;
81 let file = sema.parse(file_id);
82 let file = file.syntax();
83 let token = file.token_at_offset(position.offset).next()?;
84 let token = sema.descend_into_macros(token);
85 let syntax = token.parent();
87 let mut calls = CallLocations::default();
91 .filter_map(|node| FnCallNode::with_node_exact(&node))
92 .filter_map(|call_node| {
93 let name_ref = call_node.name_ref()?;
95 if let Some(func_target) = match &call_node {
96 FnCallNode::CallExpr(expr) => {
97 //FIXME: Type::as_callable is broken
98 let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
99 match callable.kind() {
100 hir::CallableKind::Function(it) => {
101 let fn_def: hir::Function = it.into();
102 let nav = fn_def.to_nav(db);
108 FnCallNode::MethodCallExpr(expr) => {
109 let function = sema.resolve_method_call(&expr)?;
110 Some(function.to_nav(db))
113 Some((func_target, name_ref.syntax().text_range()))
118 .for_each(|(nav, range)| calls.add(&nav, range));
120 Some(calls.into_items())
124 struct CallLocations {
125 funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
129 fn add(&mut self, target: &NavigationTarget, range: TextRange) {
130 self.funcs.entry(target.clone()).or_default().push(range);
133 fn into_items(self) -> Vec<CallItem> {
134 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
140 use base_db::FilePosition;
147 expected_incoming: &[&str],
148 expected_outgoing: &[&str],
150 let (analysis, pos) = fixture::position(ra_fixture);
152 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
153 assert_eq!(navs.len(), 1);
154 let nav = navs.pop().unwrap();
155 nav.assert_match(expected);
158 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
159 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
160 assert_eq!(incoming_calls.len(), expected_incoming.len());
162 for call in 0..incoming_calls.len() {
163 incoming_calls[call].assert_match(expected_incoming[call]);
166 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
167 assert_eq!(outgoing_calls.len(), expected_outgoing.len());
169 for call in 0..outgoing_calls.len() {
170 outgoing_calls[call].assert_match(expected_outgoing[call]);
175 fn test_call_hierarchy_on_ref() {
184 "callee FN FileId(0) 0..14 3..9",
185 &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
191 fn test_call_hierarchy_on_def() {
200 "callee FN FileId(0) 0..14 3..9",
201 &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
207 fn test_call_hierarchy_in_same_fn() {
217 "callee FN FileId(0) 0..14 3..9",
218 &["caller FN FileId(0) 15..58 18..24 : [33..39, 47..53]"],
224 fn test_call_hierarchy_in_different_fn() {
237 "callee FN FileId(0) 0..14 3..9",
239 "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
240 "caller2 FN FileId(0) 47..77 50..57 : [66..72]",
247 fn test_call_hierarchy_in_tests_mod() {
266 "callee FN FileId(0) 0..14 3..9",
268 "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
269 "test_caller FN FileId(0) 95..149 110..121 : [134..140]",
276 fn test_call_hierarchy_in_different_files() {
290 "callee FN FileId(1) 0..18 7..13",
291 &["caller FN FileId(0) 27..56 30..36 : [45..51]"],
297 fn test_call_hierarchy_outgoing() {
307 "caller FN FileId(0) 15..58 18..24",
309 &["callee FN FileId(0) 0..14 3..9 : [33..39, 47..53]"],
314 fn test_call_hierarchy_outgoing_in_different_files() {
328 "caller FN FileId(0) 27..56 30..36",
330 &["callee FN FileId(1) 0..18 7..13 : [45..51]"],
335 fn test_call_hierarchy_incoming_outgoing() {
351 "caller2 FN FileId(0) 33..64 36..43",
352 &["caller1 FN FileId(0) 0..31 3..10 : [19..26]"],
353 &["caller3 FN FileId(0) 66..83 69..76 : [52..59]"],
358 fn test_call_hierarchy_issue_5103() {
371 "a FN FileId(0) 0..18 3..4",
372 &["main FN FileId(0) 31..52 34..38 : [47..48]"],
373 &["b FN FileId(0) 20..29 23..24 : [13..14]"],
388 "b FN FileId(0) 20..29 23..24",
389 &["a FN FileId(0) 0..18 3..4 : [13..14]"],