]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide / src / call_hierarchy.rs
CommitLineData
064997fb
FG
1//! Entry point for call-hierarchy
2
3use hir::Semantics;
4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass},
6 helpers::pick_best_token,
7 search::FileReference,
8 FxIndexMap, RootDatabase,
9};
f2b60f7d 10use syntax::{ast, AstNode, SyntaxKind::IDENT, TextRange};
064997fb
FG
11
12use crate::{goto_definition, FilePosition, NavigationTarget, RangeInfo, TryToNav};
13
14#[derive(Debug, Clone)]
15pub struct CallItem {
16 pub target: NavigationTarget,
17 pub ranges: Vec<TextRange>,
18}
19
20impl CallItem {
21 #[cfg(test)]
22 pub(crate) fn debug_render(&self) -> String {
23 format!("{} : {:?}", self.target.debug_render(), self.ranges)
24 }
25}
26
27pub(crate) fn call_hierarchy(
28 db: &RootDatabase,
29 position: FilePosition,
30) -> Option<RangeInfo<Vec<NavigationTarget>>> {
31 goto_definition::goto_definition(db, position)
32}
33
34pub(crate) fn incoming_calls(
35 db: &RootDatabase,
36 FilePosition { file_id, offset }: FilePosition,
37) -> Option<Vec<CallItem>> {
38 let sema = &Semantics::new(db);
39
40 let file = sema.parse(file_id);
41 let file = file.syntax();
42 let mut calls = CallLocations::default();
43
44 let references = sema
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),
49 _ => None,
50 },
51 ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
52 NameClass::Definition(def @ Definition::Function(_)) => Some(def),
53 _ => None,
54 },
55 ast::NameLike::Lifetime(_) => None,
56 })
57 .flat_map(|func| func.usages(sema).all());
58
59 for (_, references) in references {
f25598a0
FG
60 let references =
61 references.iter().filter_map(|FileReference { name, .. }| name.as_name_ref());
064997fb
FG
62 for name in references {
63 // This target is the containing function
64 let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
65 let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
66 def.try_to_nav(sema.db)
67 });
68 if let Some(nav) = nav {
69 calls.add(nav, sema.original_range(name.syntax()).range);
70 }
71 }
72 }
73
74 Some(calls.into_items())
75}
76
77pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
78 let sema = Semantics::new(db);
79 let file_id = position.file_id;
80 let file = sema.parse(file_id);
81 let file = file.syntax();
82 let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
f2b60f7d 83 IDENT => 1,
064997fb
FG
84 _ => 0,
85 })?;
86 let mut calls = CallLocations::default();
87
88 sema.descend_into_macros(token)
89 .into_iter()
90 .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
91 .filter_map(|item| match item {
92 ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
93 ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
94 ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
95 _ => None,
96 })
97 .flatten()
98 .filter_map(ast::CallableExpr::cast)
99 .filter_map(|call_node| {
100 let (nav_target, range) = match call_node {
101 ast::CallableExpr::Call(call) => {
102 let expr = call.expr()?;
103 let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
104 match callable.kind() {
105 hir::CallableKind::Function(it) => {
106 let range = expr.syntax().text_range();
107 it.try_to_nav(db).zip(Some(range))
108 }
109 _ => None,
110 }
111 }
112 ast::CallableExpr::MethodCall(expr) => {
113 let range = expr.name_ref()?.syntax().text_range();
114 let function = sema.resolve_method_call(&expr)?;
115 function.try_to_nav(db).zip(Some(range))
116 }
117 }?;
118 Some((nav_target, range))
119 })
120 .for_each(|(nav, range)| calls.add(nav, range));
121
122 Some(calls.into_items())
123}
124
125#[derive(Default)]
126struct CallLocations {
127 funcs: FxIndexMap<NavigationTarget, Vec<TextRange>>,
128}
129
130impl CallLocations {
131 fn add(&mut self, target: NavigationTarget, range: TextRange) {
132 self.funcs.entry(target).or_default().push(range);
133 }
134
135 fn into_items(self) -> Vec<CallItem> {
136 self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use expect_test::{expect, Expect};
143 use ide_db::base_db::FilePosition;
144 use itertools::Itertools;
145
146 use crate::fixture;
147
148 fn check_hierarchy(
149 ra_fixture: &str,
150 expected: Expect,
151 expected_incoming: Expect,
152 expected_outgoing: Expect,
153 ) {
154 let (analysis, pos) = fixture::position(ra_fixture);
155
156 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
157 assert_eq!(navs.len(), 1);
158 let nav = navs.pop().unwrap();
159 expected.assert_eq(&nav.debug_render());
160
161 let item_pos =
162 FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
163 let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
164 expected_incoming
165 .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
166
167 let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
168 expected_outgoing
169 .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
170 }
171
172 #[test]
173 fn test_call_hierarchy_on_ref() {
174 check_hierarchy(
175 r#"
176//- /lib.rs
177fn callee() {}
178fn caller() {
179 call$0ee();
180}
181"#,
182 expect![["callee Function FileId(0) 0..14 3..9"]],
183 expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
184 expect![[]],
185 );
186 }
187
188 #[test]
189 fn test_call_hierarchy_on_def() {
190 check_hierarchy(
191 r#"
192//- /lib.rs
193fn call$0ee() {}
194fn caller() {
195 callee();
196}
197"#,
198 expect![["callee Function FileId(0) 0..14 3..9"]],
199 expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
200 expect![[]],
201 );
202 }
203
204 #[test]
205 fn test_call_hierarchy_in_same_fn() {
206 check_hierarchy(
207 r#"
208//- /lib.rs
209fn callee() {}
210fn caller() {
211 call$0ee();
212 callee();
213}
214"#,
215 expect![["callee Function FileId(0) 0..14 3..9"]],
216 expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
217 expect![[]],
218 );
219 }
220
221 #[test]
222 fn test_call_hierarchy_in_different_fn() {
223 check_hierarchy(
224 r#"
225//- /lib.rs
226fn callee() {}
227fn caller1() {
228 call$0ee();
229}
230
231fn caller2() {
232 callee();
233}
234"#,
235 expect![["callee Function FileId(0) 0..14 3..9"]],
236 expect![["
237 caller1 Function FileId(0) 15..45 18..25 : [34..40]
238 caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
239 expect![[]],
240 );
241 }
242
243 #[test]
244 fn test_call_hierarchy_in_tests_mod() {
245 check_hierarchy(
246 r#"
247//- /lib.rs cfg:test
248fn callee() {}
249fn caller1() {
250 call$0ee();
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_caller() {
259 callee();
260 }
261}
262"#,
263 expect![["callee Function FileId(0) 0..14 3..9"]],
264 expect![[r#"
265 caller1 Function FileId(0) 15..45 18..25 : [34..40]
266 test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
267 expect![[]],
268 );
269 }
270
271 #[test]
272 fn test_call_hierarchy_in_different_files() {
273 check_hierarchy(
274 r#"
275//- /lib.rs
276mod foo;
277use foo::callee;
278
279fn caller() {
280 call$0ee();
281}
282
283//- /foo/mod.rs
284pub fn callee() {}
285"#,
286 expect![["callee Function FileId(1) 0..18 7..13"]],
287 expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
288 expect![[]],
289 );
290 }
291
292 #[test]
293 fn test_call_hierarchy_outgoing() {
294 check_hierarchy(
295 r#"
296//- /lib.rs
297fn callee() {}
298fn call$0er() {
299 callee();
300 callee();
301}
302"#,
303 expect![["caller Function FileId(0) 15..58 18..24"]],
304 expect![[]],
305 expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
306 );
307 }
308
309 #[test]
310 fn test_call_hierarchy_outgoing_in_different_files() {
311 check_hierarchy(
312 r#"
313//- /lib.rs
314mod foo;
315use foo::callee;
316
317fn call$0er() {
318 callee();
319}
320
321//- /foo/mod.rs
322pub fn callee() {}
323"#,
324 expect![["caller Function FileId(0) 27..56 30..36"]],
325 expect![[]],
326 expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
327 );
328 }
329
330 #[test]
331 fn test_call_hierarchy_incoming_outgoing() {
332 check_hierarchy(
333 r#"
334//- /lib.rs
335fn caller1() {
336 call$0er2();
337}
338
339fn caller2() {
340 caller3();
341}
342
343fn caller3() {
344
345}
346"#,
347 expect![["caller2 Function FileId(0) 33..64 36..43"]],
348 expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
349 expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
350 );
351 }
352
353 #[test]
354 fn test_call_hierarchy_issue_5103() {
355 check_hierarchy(
356 r#"
357fn a() {
358 b()
359}
360
361fn b() {}
362
363fn main() {
364 a$0()
365}
366"#,
367 expect![["a Function FileId(0) 0..18 3..4"]],
368 expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
369 expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
370 );
371
372 check_hierarchy(
373 r#"
374fn a() {
375 b$0()
376}
377
378fn b() {}
379
380fn main() {
381 a()
382}
383"#,
384 expect![["b Function FileId(0) 20..29 23..24"]],
385 expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
386 expect![[]],
387 );
388 }
389
390 #[test]
391 fn test_call_hierarchy_in_macros_incoming() {
392 check_hierarchy(
393 r#"
394macro_rules! define {
395 ($ident:ident) => {
396 fn $ident {}
397 }
398}
399macro_rules! call {
400 ($ident:ident) => {
401 $ident()
402 }
403}
404define!(callee)
405fn caller() {
406 call!(call$0ee);
407}
408"#,
409 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
410 expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
411 expect![[]],
412 );
413 check_hierarchy(
414 r#"
415macro_rules! define {
416 ($ident:ident) => {
417 fn $ident {}
418 }
419}
420macro_rules! call {
421 ($ident:ident) => {
422 $ident()
423 }
424}
425define!(cal$0lee)
426fn caller() {
427 call!(callee);
428}
429"#,
430 expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
431 expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
432 expect![[]],
433 );
434 }
435
436 #[test]
437 fn test_call_hierarchy_in_macros_outgoing() {
438 check_hierarchy(
439 r#"
440macro_rules! define {
441 ($ident:ident) => {
442 fn $ident {}
443 }
444}
445macro_rules! call {
446 ($ident:ident) => {
447 $ident()
448 }
449}
450define!(callee)
451fn caller$0() {
452 call!(callee);
453}
454"#,
455 expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
456 expect![[]],
457 // FIXME
458 expect![[]],
459 );
460 }
f25598a0
FG
461
462 #[test]
463 fn test_trait_method_call_hierarchy() {
464 check_hierarchy(
465 r#"
466trait T1 {
467 fn call$0ee();
468}
469
470struct S1;
471
472impl T1 for S1 {
473 fn callee() {}
474}
475
476fn caller() {
477 S1::callee();
478}
479"#,
480 expect![["callee Function FileId(0) 15..27 18..24"]],
481 expect![["caller Function FileId(0) 82..115 85..91 : [104..110]"]],
482 expect![[]],
483 );
484 }
064997fb 485}