]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/call_hierarchy.rs
Merge #10802
[rust.git] / crates / ide / src / call_hierarchy.rs
1 //! Entry point for call-hierarchy
2
3 use hir::Semantics;
4 use ide_db::{
5     defs::{Definition, NameClass, NameRefClass},
6     helpers::pick_best_token,
7     search::FileReference,
8     FxIndexMap, RootDatabase,
9 };
10 use syntax::{ast, AstNode, SyntaxKind::NAME, TextRange};
11
12 use crate::{goto_definition, FilePosition, NavigationTarget, RangeInfo, TryToNav};
13
14 #[derive(Debug, Clone)]
15 pub struct CallItem {
16     pub target: NavigationTarget,
17     pub ranges: Vec<TextRange>,
18 }
19
20 impl CallItem {
21     #[cfg(test)]
22     pub(crate) fn debug_render(&self) -> String {
23         format!("{} : {:?}", self.target.debug_render(), self.ranges)
24     }
25 }
26
27 pub(crate) fn call_hierarchy(
28     db: &RootDatabase,
29     position: FilePosition,
30 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
31     goto_definition::goto_definition(db, position)
32 }
33
34 pub(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 {
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)
66             });
67             if let Some(nav) = nav {
68                 calls.add(nav, sema.original_range(name.syntax()).range);
69             }
70         }
71     }
72
73     Some(calls.into_items())
74 }
75
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 {
82         NAME => 1,
83         _ => 0,
84     })?;
85     let mut calls = CallLocations::default();
86
87     sema.descend_into_macros(token)
88         .into_iter()
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()),
94             _ => None,
95         })
96         .flatten()
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))
107                         }
108                         _ => None,
109                     }
110                 }
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))
115                 }
116             }?;
117             Some((nav_target, range))
118         })
119         .for_each(|(nav, range)| calls.add(nav, range));
120
121     Some(calls.into_items())
122 }
123
124 #[derive(Default)]
125 struct CallLocations {
126     funcs: FxIndexMap<NavigationTarget, Vec<TextRange>>,
127 }
128
129 impl CallLocations {
130     fn add(&mut self, target: NavigationTarget, range: TextRange) {
131         self.funcs.entry(target).or_default().push(range);
132     }
133
134     fn into_items(self) -> Vec<CallItem> {
135         self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
136     }
137 }
138
139 #[cfg(test)]
140 mod tests {
141     use expect_test::{expect, Expect};
142     use ide_db::base_db::FilePosition;
143     use itertools::Itertools;
144
145     use crate::fixture;
146
147     fn check_hierarchy(
148         ra_fixture: &str,
149         expected: Expect,
150         expected_incoming: Expect,
151         expected_outgoing: Expect,
152     ) {
153         let (analysis, pos) = fixture::position(ra_fixture);
154
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());
159
160         let item_pos =
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();
163         expected_incoming
164             .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
165
166         let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
167         expected_outgoing
168             .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
169     }
170
171     #[test]
172     fn test_call_hierarchy_on_ref() {
173         check_hierarchy(
174             r#"
175 //- /lib.rs
176 fn callee() {}
177 fn caller() {
178     call$0ee();
179 }
180 "#,
181             expect![["callee Function FileId(0) 0..14 3..9"]],
182             expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
183             expect![[]],
184         );
185     }
186
187     #[test]
188     fn test_call_hierarchy_on_def() {
189         check_hierarchy(
190             r#"
191 //- /lib.rs
192 fn call$0ee() {}
193 fn caller() {
194     callee();
195 }
196 "#,
197             expect![["callee Function FileId(0) 0..14 3..9"]],
198             expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
199             expect![[]],
200         );
201     }
202
203     #[test]
204     fn test_call_hierarchy_in_same_fn() {
205         check_hierarchy(
206             r#"
207 //- /lib.rs
208 fn callee() {}
209 fn caller() {
210     call$0ee();
211     callee();
212 }
213 "#,
214             expect![["callee Function FileId(0) 0..14 3..9"]],
215             expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
216             expect![[]],
217         );
218     }
219
220     #[test]
221     fn test_call_hierarchy_in_different_fn() {
222         check_hierarchy(
223             r#"
224 //- /lib.rs
225 fn callee() {}
226 fn caller1() {
227     call$0ee();
228 }
229
230 fn caller2() {
231     callee();
232 }
233 "#,
234             expect![["callee Function FileId(0) 0..14 3..9"]],
235             expect![["
236                 caller1 Function FileId(0) 15..45 18..25 : [34..40]
237                 caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
238             expect![[]],
239         );
240     }
241
242     #[test]
243     fn test_call_hierarchy_in_tests_mod() {
244         check_hierarchy(
245             r#"
246 //- /lib.rs cfg:test
247 fn callee() {}
248 fn caller1() {
249     call$0ee();
250 }
251
252 #[cfg(test)]
253 mod tests {
254     use super::*;
255
256     #[test]
257     fn test_caller() {
258         callee();
259     }
260 }
261 "#,
262             expect![["callee Function FileId(0) 0..14 3..9"]],
263             expect![[r#"
264                 caller1 Function FileId(0) 15..45 18..25 : [34..40]
265                 test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
266             expect![[]],
267         );
268     }
269
270     #[test]
271     fn test_call_hierarchy_in_different_files() {
272         check_hierarchy(
273             r#"
274 //- /lib.rs
275 mod foo;
276 use foo::callee;
277
278 fn caller() {
279     call$0ee();
280 }
281
282 //- /foo/mod.rs
283 pub fn callee() {}
284 "#,
285             expect![["callee Function FileId(1) 0..18 7..13"]],
286             expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
287             expect![[]],
288         );
289     }
290
291     #[test]
292     fn test_call_hierarchy_outgoing() {
293         check_hierarchy(
294             r#"
295 //- /lib.rs
296 fn callee() {}
297 fn call$0er() {
298     callee();
299     callee();
300 }
301 "#,
302             expect![["caller Function FileId(0) 15..58 18..24"]],
303             expect![[]],
304             expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
305         );
306     }
307
308     #[test]
309     fn test_call_hierarchy_outgoing_in_different_files() {
310         check_hierarchy(
311             r#"
312 //- /lib.rs
313 mod foo;
314 use foo::callee;
315
316 fn call$0er() {
317     callee();
318 }
319
320 //- /foo/mod.rs
321 pub fn callee() {}
322 "#,
323             expect![["caller Function FileId(0) 27..56 30..36"]],
324             expect![[]],
325             expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
326         );
327     }
328
329     #[test]
330     fn test_call_hierarchy_incoming_outgoing() {
331         check_hierarchy(
332             r#"
333 //- /lib.rs
334 fn caller1() {
335     call$0er2();
336 }
337
338 fn caller2() {
339     caller3();
340 }
341
342 fn caller3() {
343
344 }
345 "#,
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]"]],
349         );
350     }
351
352     #[test]
353     fn test_call_hierarchy_issue_5103() {
354         check_hierarchy(
355             r#"
356 fn a() {
357     b()
358 }
359
360 fn b() {}
361
362 fn main() {
363     a$0()
364 }
365 "#,
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]"]],
369         );
370
371         check_hierarchy(
372             r#"
373 fn a() {
374     b$0()
375 }
376
377 fn b() {}
378
379 fn main() {
380     a()
381 }
382 "#,
383             expect![["b Function FileId(0) 20..29 23..24"]],
384             expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
385             expect![[]],
386         );
387     }
388
389     #[test]
390     fn test_call_hierarchy_in_macros_incoming() {
391         check_hierarchy(
392             r#"
393 macro_rules! define {
394     ($ident:ident) => {
395         fn $ident {}
396     }
397 }
398 macro_rules! call {
399     ($ident:ident) => {
400         $ident()
401     }
402 }
403 define!(callee)
404 fn caller() {
405     call!(call$0ee);
406 }
407 "#,
408             expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
409             expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
410             expect![[]],
411         );
412         check_hierarchy(
413             r#"
414 macro_rules! define {
415     ($ident:ident) => {
416         fn $ident {}
417     }
418 }
419 macro_rules! call {
420     ($ident:ident) => {
421         $ident()
422     }
423 }
424 define!(cal$0lee)
425 fn caller() {
426     call!(callee);
427 }
428 "#,
429             expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
430             expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
431             expect![[]],
432         );
433     }
434
435     #[test]
436     fn test_call_hierarchy_in_macros_outgoing() {
437         check_hierarchy(
438             r#"
439 macro_rules! define {
440     ($ident:ident) => {
441         fn $ident {}
442     }
443 }
444 macro_rules! call {
445     ($ident:ident) => {
446         $ident()
447     }
448 }
449 define!(callee)
450 fn caller$0() {
451     call!(callee);
452 }
453 "#,
454             expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
455             expect![[]],
456             // FIXME
457             expect![[]],
458         );
459     }
460 }