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