diff options
author | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
commit | c26c911ec1e6c2ad1dcb7d155a6a1d528839ad1a (patch) | |
tree | 7cff36c38234be0afb65273146d8247083a5cfeb /crates/ide/src/call_hierarchy.rs | |
parent | 3c018bf84de5c693b5ee1c6bec0fed3b201c2060 (diff) | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Merge branch 'master' into add-disable-diagnostics
Diffstat (limited to 'crates/ide/src/call_hierarchy.rs')
-rw-r--r-- | crates/ide/src/call_hierarchy.rs | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs new file mode 100644 index 000000000..58e26b94c --- /dev/null +++ b/crates/ide/src/call_hierarchy.rs | |||
@@ -0,0 +1,393 @@ | |||
1 | //! Entry point for call-hierarchy | ||
2 | |||
3 | use indexmap::IndexMap; | ||
4 | |||
5 | use hir::Semantics; | ||
6 | use ide_db::RootDatabase; | ||
7 | use syntax::{ast, match_ast, AstNode, TextRange}; | ||
8 | |||
9 | use crate::{ | ||
10 | call_info::FnCallNode, display::ToNav, goto_definition, references, FilePosition, | ||
11 | NavigationTarget, RangeInfo, | ||
12 | }; | ||
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 assert_match(&self, expected: &str) { | ||
23 | let actual = self.debug_render(); | ||
24 | test_utils::assert_eq_text!(expected.trim(), actual.trim(),); | ||
25 | } | ||
26 | |||
27 | #[cfg(test)] | ||
28 | pub(crate) fn debug_render(&self) -> String { | ||
29 | format!("{} : {:?}", self.target.debug_render(), self.ranges) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | pub(crate) fn call_hierarchy( | ||
34 | db: &RootDatabase, | ||
35 | position: FilePosition, | ||
36 | ) -> Option<RangeInfo<Vec<NavigationTarget>>> { | ||
37 | goto_definition::goto_definition(db, position) | ||
38 | } | ||
39 | |||
40 | pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> { | ||
41 | let sema = Semantics::new(db); | ||
42 | |||
43 | // 1. Find all refs | ||
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)?; | ||
47 | |||
48 | let mut calls = CallLocations::default(); | ||
49 | |||
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(); | ||
57 | |||
58 | // This target is the containing function | ||
59 | if let Some(nav) = syntax.ancestors().find_map(|node| { | ||
60 | match_ast! { | ||
61 | match node { | ||
62 | ast::Fn(it) => { | ||
63 | let def = sema.to_def(&it)?; | ||
64 | Some(def.to_nav(sema.db)) | ||
65 | }, | ||
66 | _ => None, | ||
67 | } | ||
68 | } | ||
69 | }) { | ||
70 | let relative_range = reference.file_range.range; | ||
71 | calls.add(&nav, relative_range); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | Some(calls.into_items()) | ||
76 | } | ||
77 | |||
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(); | ||
86 | |||
87 | let mut calls = CallLocations::default(); | ||
88 | |||
89 | syntax | ||
90 | .descendants() | ||
91 | .filter_map(|node| FnCallNode::with_node_exact(&node)) | ||
92 | .filter_map(|call_node| { | ||
93 | let name_ref = call_node.name_ref()?; | ||
94 | |||
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); | ||
103 | Some(nav) | ||
104 | } | ||
105 | _ => None, | ||
106 | } | ||
107 | } | ||
108 | FnCallNode::MethodCallExpr(expr) => { | ||
109 | let function = sema.resolve_method_call(&expr)?; | ||
110 | Some(function.to_nav(db)) | ||
111 | } | ||
112 | } { | ||
113 | Some((func_target, name_ref.syntax().text_range())) | ||
114 | } else { | ||
115 | None | ||
116 | } | ||
117 | }) | ||
118 | .for_each(|(nav, range)| calls.add(&nav, range)); | ||
119 | |||
120 | Some(calls.into_items()) | ||
121 | } | ||
122 | |||
123 | #[derive(Default)] | ||
124 | struct CallLocations { | ||
125 | funcs: IndexMap<NavigationTarget, Vec<TextRange>>, | ||
126 | } | ||
127 | |||
128 | impl CallLocations { | ||
129 | fn add(&mut self, target: &NavigationTarget, range: TextRange) { | ||
130 | self.funcs.entry(target.clone()).or_default().push(range); | ||
131 | } | ||
132 | |||
133 | fn into_items(self) -> Vec<CallItem> { | ||
134 | self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect() | ||
135 | } | ||
136 | } | ||
137 | |||
138 | #[cfg(test)] | ||
139 | mod tests { | ||
140 | use base_db::FilePosition; | ||
141 | |||
142 | use crate::mock_analysis::analysis_and_position; | ||
143 | |||
144 | fn check_hierarchy( | ||
145 | ra_fixture: &str, | ||
146 | expected: &str, | ||
147 | expected_incoming: &[&str], | ||
148 | expected_outgoing: &[&str], | ||
149 | ) { | ||
150 | let (analysis, pos) = analysis_and_position(ra_fixture); | ||
151 | |||
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); | ||
156 | |||
157 | let item_pos = | ||
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()); | ||
161 | |||
162 | for call in 0..incoming_calls.len() { | ||
163 | incoming_calls[call].assert_match(expected_incoming[call]); | ||
164 | } | ||
165 | |||
166 | let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap(); | ||
167 | assert_eq!(outgoing_calls.len(), expected_outgoing.len()); | ||
168 | |||
169 | for call in 0..outgoing_calls.len() { | ||
170 | outgoing_calls[call].assert_match(expected_outgoing[call]); | ||
171 | } | ||
172 | } | ||
173 | |||
174 | #[test] | ||
175 | fn test_call_hierarchy_on_ref() { | ||
176 | check_hierarchy( | ||
177 | r#" | ||
178 | //- /lib.rs | ||
179 | fn callee() {} | ||
180 | fn caller() { | ||
181 | call<|>ee(); | ||
182 | } | ||
183 | "#, | ||
184 | "callee FN FileId(1) 0..14 3..9", | ||
185 | &["caller FN FileId(1) 15..44 18..24 : [33..39]"], | ||
186 | &[], | ||
187 | ); | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn test_call_hierarchy_on_def() { | ||
192 | check_hierarchy( | ||
193 | r#" | ||
194 | //- /lib.rs | ||
195 | fn call<|>ee() {} | ||
196 | fn caller() { | ||
197 | callee(); | ||
198 | } | ||
199 | "#, | ||
200 | "callee FN FileId(1) 0..14 3..9", | ||
201 | &["caller FN FileId(1) 15..44 18..24 : [33..39]"], | ||
202 | &[], | ||
203 | ); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_call_hierarchy_in_same_fn() { | ||
208 | check_hierarchy( | ||
209 | r#" | ||
210 | //- /lib.rs | ||
211 | fn callee() {} | ||
212 | fn caller() { | ||
213 | call<|>ee(); | ||
214 | callee(); | ||
215 | } | ||
216 | "#, | ||
217 | "callee FN FileId(1) 0..14 3..9", | ||
218 | &["caller FN FileId(1) 15..58 18..24 : [33..39, 47..53]"], | ||
219 | &[], | ||
220 | ); | ||
221 | } | ||
222 | |||
223 | #[test] | ||
224 | fn test_call_hierarchy_in_different_fn() { | ||
225 | check_hierarchy( | ||
226 | r#" | ||
227 | //- /lib.rs | ||
228 | fn callee() {} | ||
229 | fn caller1() { | ||
230 | call<|>ee(); | ||
231 | } | ||
232 | |||
233 | fn caller2() { | ||
234 | callee(); | ||
235 | } | ||
236 | "#, | ||
237 | "callee FN FileId(1) 0..14 3..9", | ||
238 | &[ | ||
239 | "caller1 FN FileId(1) 15..45 18..25 : [34..40]", | ||
240 | "caller2 FN FileId(1) 47..77 50..57 : [66..72]", | ||
241 | ], | ||
242 | &[], | ||
243 | ); | ||
244 | } | ||
245 | |||
246 | #[test] | ||
247 | fn test_call_hierarchy_in_tests_mod() { | ||
248 | check_hierarchy( | ||
249 | r#" | ||
250 | //- /lib.rs cfg:test | ||
251 | fn callee() {} | ||
252 | fn caller1() { | ||
253 | call<|>ee(); | ||
254 | } | ||
255 | |||
256 | #[cfg(test)] | ||
257 | mod tests { | ||
258 | use super::*; | ||
259 | |||
260 | #[test] | ||
261 | fn test_caller() { | ||
262 | callee(); | ||
263 | } | ||
264 | } | ||
265 | "#, | ||
266 | "callee FN FileId(1) 0..14 3..9", | ||
267 | &[ | ||
268 | "caller1 FN FileId(1) 15..45 18..25 : [34..40]", | ||
269 | "test_caller FN FileId(1) 95..149 110..121 : [134..140]", | ||
270 | ], | ||
271 | &[], | ||
272 | ); | ||
273 | } | ||
274 | |||
275 | #[test] | ||
276 | fn test_call_hierarchy_in_different_files() { | ||
277 | check_hierarchy( | ||
278 | r#" | ||
279 | //- /lib.rs | ||
280 | mod foo; | ||
281 | use foo::callee; | ||
282 | |||
283 | fn caller() { | ||
284 | call<|>ee(); | ||
285 | } | ||
286 | |||
287 | //- /foo/mod.rs | ||
288 | pub fn callee() {} | ||
289 | "#, | ||
290 | "callee FN FileId(2) 0..18 7..13", | ||
291 | &["caller FN FileId(1) 27..56 30..36 : [45..51]"], | ||
292 | &[], | ||
293 | ); | ||
294 | } | ||
295 | |||
296 | #[test] | ||
297 | fn test_call_hierarchy_outgoing() { | ||
298 | check_hierarchy( | ||
299 | r#" | ||
300 | //- /lib.rs | ||
301 | fn callee() {} | ||
302 | fn call<|>er() { | ||
303 | callee(); | ||
304 | callee(); | ||
305 | } | ||
306 | "#, | ||
307 | "caller FN FileId(1) 15..58 18..24", | ||
308 | &[], | ||
309 | &["callee FN FileId(1) 0..14 3..9 : [33..39, 47..53]"], | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_call_hierarchy_outgoing_in_different_files() { | ||
315 | check_hierarchy( | ||
316 | r#" | ||
317 | //- /lib.rs | ||
318 | mod foo; | ||
319 | use foo::callee; | ||
320 | |||
321 | fn call<|>er() { | ||
322 | callee(); | ||
323 | } | ||
324 | |||
325 | //- /foo/mod.rs | ||
326 | pub fn callee() {} | ||
327 | "#, | ||
328 | "caller FN FileId(1) 27..56 30..36", | ||
329 | &[], | ||
330 | &["callee FN FileId(2) 0..18 7..13 : [45..51]"], | ||
331 | ); | ||
332 | } | ||
333 | |||
334 | #[test] | ||
335 | fn test_call_hierarchy_incoming_outgoing() { | ||
336 | check_hierarchy( | ||
337 | r#" | ||
338 | //- /lib.rs | ||
339 | fn caller1() { | ||
340 | call<|>er2(); | ||
341 | } | ||
342 | |||
343 | fn caller2() { | ||
344 | caller3(); | ||
345 | } | ||
346 | |||
347 | fn caller3() { | ||
348 | |||
349 | } | ||
350 | "#, | ||
351 | "caller2 FN FileId(1) 33..64 36..43", | ||
352 | &["caller1 FN FileId(1) 0..31 3..10 : [19..26]"], | ||
353 | &["caller3 FN FileId(1) 66..83 69..76 : [52..59]"], | ||
354 | ); | ||
355 | } | ||
356 | |||
357 | #[test] | ||
358 | fn test_call_hierarchy_issue_5103() { | ||
359 | check_hierarchy( | ||
360 | r#" | ||
361 | fn a() { | ||
362 | b() | ||
363 | } | ||
364 | |||
365 | fn b() {} | ||
366 | |||
367 | fn main() { | ||
368 | a<|>() | ||
369 | } | ||
370 | "#, | ||
371 | "a FN FileId(1) 0..18 3..4", | ||
372 | &["main FN FileId(1) 31..52 34..38 : [47..48]"], | ||
373 | &["b FN FileId(1) 20..29 23..24 : [13..14]"], | ||
374 | ); | ||
375 | |||
376 | check_hierarchy( | ||
377 | r#" | ||
378 | fn a() { | ||
379 | b<|>() | ||
380 | } | ||
381 | |||
382 | fn b() {} | ||
383 | |||
384 | fn main() { | ||
385 | a() | ||
386 | } | ||
387 | "#, | ||
388 | "b FN FileId(1) 20..29 23..24", | ||
389 | &["a FN FileId(1) 0..18 3..4 : [13..14]"], | ||
390 | &[], | ||
391 | ); | ||
392 | } | ||
393 | } | ||