diff options
Diffstat (limited to 'crates/ra_ide/src/call_info.rs')
-rw-r--r-- | crates/ra_ide/src/call_info.rs | 592 |
1 files changed, 592 insertions, 0 deletions
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs new file mode 100644 index 000000000..d559dc4d0 --- /dev/null +++ b/crates/ra_ide/src/call_info.rs | |||
@@ -0,0 +1,592 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_db::SourceDatabase; | ||
4 | use ra_syntax::{ | ||
5 | algo::ancestors_at_offset, | ||
6 | ast::{self, ArgListOwner}, | ||
7 | match_ast, AstNode, SyntaxNode, TextUnit, | ||
8 | }; | ||
9 | use test_utils::tested_by; | ||
10 | |||
11 | use crate::{db::RootDatabase, CallInfo, FilePosition, FunctionSignature}; | ||
12 | |||
13 | /// Computes parameter information for the given call expression. | ||
14 | pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> { | ||
15 | let parse = db.parse(position.file_id); | ||
16 | let syntax = parse.tree().syntax().clone(); | ||
17 | |||
18 | // Find the calling expression and it's NameRef | ||
19 | let calling_node = FnCallNode::with_node(&syntax, position.offset)?; | ||
20 | let name_ref = calling_node.name_ref()?; | ||
21 | let name_ref = hir::Source::new(position.file_id.into(), name_ref.syntax()); | ||
22 | |||
23 | let analyzer = hir::SourceAnalyzer::new(db, name_ref, None); | ||
24 | let (mut call_info, has_self) = match &calling_node { | ||
25 | FnCallNode::CallExpr(expr) => { | ||
26 | //FIXME: Type::as_callable is broken | ||
27 | let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; | ||
28 | match callable_def { | ||
29 | hir::CallableDef::FunctionId(it) => { | ||
30 | let fn_def = it.into(); | ||
31 | (CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db)) | ||
32 | } | ||
33 | hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false), | ||
34 | hir::CallableDef::EnumVariantId(it) => { | ||
35 | (CallInfo::with_enum_variant(db, it.into())?, false) | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | FnCallNode::MethodCallExpr(expr) => { | ||
40 | let function = analyzer.resolve_method_call(&expr)?; | ||
41 | (CallInfo::with_fn(db, function), function.has_self_param(db)) | ||
42 | } | ||
43 | FnCallNode::MacroCallExpr(expr) => { | ||
44 | let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?; | ||
45 | (CallInfo::with_macro(db, macro_def)?, false) | ||
46 | } | ||
47 | }; | ||
48 | |||
49 | // If we have a calling expression let's find which argument we are on | ||
50 | let num_params = call_info.parameters().len(); | ||
51 | |||
52 | if num_params == 1 { | ||
53 | if !has_self { | ||
54 | call_info.active_parameter = Some(0); | ||
55 | } | ||
56 | } else if num_params > 1 { | ||
57 | // Count how many parameters into the call we are. | ||
58 | if let Some(arg_list) = calling_node.arg_list() { | ||
59 | // Number of arguments specified at the call site | ||
60 | let num_args_at_callsite = arg_list.args().count(); | ||
61 | |||
62 | let arg_list_range = arg_list.syntax().text_range(); | ||
63 | if !arg_list_range.contains_inclusive(position.offset) { | ||
64 | tested_by!(call_info_bad_offset); | ||
65 | return None; | ||
66 | } | ||
67 | |||
68 | let mut param = std::cmp::min( | ||
69 | num_args_at_callsite, | ||
70 | arg_list | ||
71 | .args() | ||
72 | .take_while(|arg| arg.syntax().text_range().end() < position.offset) | ||
73 | .count(), | ||
74 | ); | ||
75 | |||
76 | // If we are in a method account for `self` | ||
77 | if has_self { | ||
78 | param += 1; | ||
79 | } | ||
80 | |||
81 | call_info.active_parameter = Some(param); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | Some(call_info) | ||
86 | } | ||
87 | |||
88 | #[derive(Debug)] | ||
89 | enum FnCallNode { | ||
90 | CallExpr(ast::CallExpr), | ||
91 | MethodCallExpr(ast::MethodCallExpr), | ||
92 | MacroCallExpr(ast::MacroCall), | ||
93 | } | ||
94 | |||
95 | impl FnCallNode { | ||
96 | fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option<FnCallNode> { | ||
97 | ancestors_at_offset(syntax, offset).find_map(|node| { | ||
98 | match_ast! { | ||
99 | match node { | ||
100 | ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, | ||
101 | ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, | ||
102 | ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, | ||
103 | _ => { None }, | ||
104 | } | ||
105 | } | ||
106 | }) | ||
107 | } | ||
108 | |||
109 | fn name_ref(&self) -> Option<ast::NameRef> { | ||
110 | match self { | ||
111 | FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { | ||
112 | ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, | ||
113 | _ => return None, | ||
114 | }), | ||
115 | |||
116 | FnCallNode::MethodCallExpr(call_expr) => { | ||
117 | call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0) | ||
118 | } | ||
119 | |||
120 | FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(), | ||
121 | } | ||
122 | } | ||
123 | |||
124 | fn arg_list(&self) -> Option<ast::ArgList> { | ||
125 | match self { | ||
126 | FnCallNode::CallExpr(expr) => expr.arg_list(), | ||
127 | FnCallNode::MethodCallExpr(expr) => expr.arg_list(), | ||
128 | FnCallNode::MacroCallExpr(_) => None, | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | impl CallInfo { | ||
134 | fn with_fn(db: &RootDatabase, function: hir::Function) -> Self { | ||
135 | let signature = FunctionSignature::from_hir(db, function); | ||
136 | |||
137 | CallInfo { signature, active_parameter: None } | ||
138 | } | ||
139 | |||
140 | fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option<Self> { | ||
141 | let signature = FunctionSignature::from_struct(db, st)?; | ||
142 | |||
143 | Some(CallInfo { signature, active_parameter: None }) | ||
144 | } | ||
145 | |||
146 | fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option<Self> { | ||
147 | let signature = FunctionSignature::from_enum_variant(db, variant)?; | ||
148 | |||
149 | Some(CallInfo { signature, active_parameter: None }) | ||
150 | } | ||
151 | |||
152 | fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option<Self> { | ||
153 | let signature = FunctionSignature::from_macro(db, macro_def)?; | ||
154 | |||
155 | Some(CallInfo { signature, active_parameter: None }) | ||
156 | } | ||
157 | |||
158 | fn parameters(&self) -> &[String] { | ||
159 | &self.signature.parameters | ||
160 | } | ||
161 | } | ||
162 | |||
163 | #[cfg(test)] | ||
164 | mod tests { | ||
165 | use test_utils::covers; | ||
166 | |||
167 | use crate::mock_analysis::single_file_with_position; | ||
168 | |||
169 | use super::*; | ||
170 | |||
171 | // These are only used when testing | ||
172 | impl CallInfo { | ||
173 | fn doc(&self) -> Option<hir::Documentation> { | ||
174 | self.signature.doc.clone() | ||
175 | } | ||
176 | |||
177 | fn label(&self) -> String { | ||
178 | self.signature.to_string() | ||
179 | } | ||
180 | } | ||
181 | |||
182 | fn call_info(text: &str) -> CallInfo { | ||
183 | let (analysis, position) = single_file_with_position(text); | ||
184 | analysis.call_info(position).unwrap().unwrap() | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_fn_signature_two_args_firstx() { | ||
189 | let info = call_info( | ||
190 | r#"fn foo(x: u32, y: u32) -> u32 {x + y} | ||
191 | fn bar() { foo(<|>3, ); }"#, | ||
192 | ); | ||
193 | |||
194 | assert_eq!(info.parameters(), ["x: u32", "y: u32"]); | ||
195 | assert_eq!(info.active_parameter, Some(0)); | ||
196 | } | ||
197 | |||
198 | #[test] | ||
199 | fn test_fn_signature_two_args_second() { | ||
200 | let info = call_info( | ||
201 | r#"fn foo(x: u32, y: u32) -> u32 {x + y} | ||
202 | fn bar() { foo(3, <|>); }"#, | ||
203 | ); | ||
204 | |||
205 | assert_eq!(info.parameters(), ["x: u32", "y: u32"]); | ||
206 | assert_eq!(info.active_parameter, Some(1)); | ||
207 | } | ||
208 | |||
209 | #[test] | ||
210 | fn test_fn_signature_two_args_empty() { | ||
211 | let info = call_info( | ||
212 | r#"fn foo(x: u32, y: u32) -> u32 {x + y} | ||
213 | fn bar() { foo(<|>); }"#, | ||
214 | ); | ||
215 | |||
216 | assert_eq!(info.parameters(), ["x: u32", "y: u32"]); | ||
217 | assert_eq!(info.active_parameter, Some(0)); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn test_fn_signature_two_args_first_generics() { | ||
222 | let info = call_info( | ||
223 | r#"fn foo<T, U: Copy + Display>(x: T, y: U) -> u32 where T: Copy + Display, U: Debug {x + y} | ||
224 | fn bar() { foo(<|>3, ); }"#, | ||
225 | ); | ||
226 | |||
227 | assert_eq!(info.parameters(), ["x: T", "y: U"]); | ||
228 | assert_eq!( | ||
229 | info.label(), | ||
230 | r#" | ||
231 | fn foo<T, U: Copy + Display>(x: T, y: U) -> u32 | ||
232 | where T: Copy + Display, | ||
233 | U: Debug | ||
234 | "# | ||
235 | .trim() | ||
236 | ); | ||
237 | assert_eq!(info.active_parameter, Some(0)); | ||
238 | } | ||
239 | |||
240 | #[test] | ||
241 | fn test_fn_signature_no_params() { | ||
242 | let info = call_info( | ||
243 | r#"fn foo<T>() -> T where T: Copy + Display {} | ||
244 | fn bar() { foo(<|>); }"#, | ||
245 | ); | ||
246 | |||
247 | assert!(info.parameters().is_empty()); | ||
248 | assert_eq!( | ||
249 | info.label(), | ||
250 | r#" | ||
251 | fn foo<T>() -> T | ||
252 | where T: Copy + Display | ||
253 | "# | ||
254 | .trim() | ||
255 | ); | ||
256 | assert!(info.active_parameter.is_none()); | ||
257 | } | ||
258 | |||
259 | #[test] | ||
260 | fn test_fn_signature_for_impl() { | ||
261 | let info = call_info( | ||
262 | r#"struct F; impl F { pub fn new() { F{}} } | ||
263 | fn bar() {let _ : F = F::new(<|>);}"#, | ||
264 | ); | ||
265 | |||
266 | assert!(info.parameters().is_empty()); | ||
267 | assert_eq!(info.active_parameter, None); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_fn_signature_for_method_self() { | ||
272 | let info = call_info( | ||
273 | r#"struct F; | ||
274 | impl F { | ||
275 | pub fn new() -> F{ | ||
276 | F{} | ||
277 | } | ||
278 | |||
279 | pub fn do_it(&self) {} | ||
280 | } | ||
281 | |||
282 | fn bar() { | ||
283 | let f : F = F::new(); | ||
284 | f.do_it(<|>); | ||
285 | }"#, | ||
286 | ); | ||
287 | |||
288 | assert_eq!(info.parameters(), ["&self"]); | ||
289 | assert_eq!(info.active_parameter, None); | ||
290 | } | ||
291 | |||
292 | #[test] | ||
293 | fn test_fn_signature_for_method_with_arg() { | ||
294 | let info = call_info( | ||
295 | r#"struct F; | ||
296 | impl F { | ||
297 | pub fn new() -> F{ | ||
298 | F{} | ||
299 | } | ||
300 | |||
301 | pub fn do_it(&self, x: i32) {} | ||
302 | } | ||
303 | |||
304 | fn bar() { | ||
305 | let f : F = F::new(); | ||
306 | f.do_it(<|>); | ||
307 | }"#, | ||
308 | ); | ||
309 | |||
310 | assert_eq!(info.parameters(), ["&self", "x: i32"]); | ||
311 | assert_eq!(info.active_parameter, Some(1)); | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn test_fn_signature_with_docs_simple() { | ||
316 | let info = call_info( | ||
317 | r#" | ||
318 | /// test | ||
319 | // non-doc-comment | ||
320 | fn foo(j: u32) -> u32 { | ||
321 | j | ||
322 | } | ||
323 | |||
324 | fn bar() { | ||
325 | let _ = foo(<|>); | ||
326 | } | ||
327 | "#, | ||
328 | ); | ||
329 | |||
330 | assert_eq!(info.parameters(), ["j: u32"]); | ||
331 | assert_eq!(info.active_parameter, Some(0)); | ||
332 | assert_eq!(info.label(), "fn foo(j: u32) -> u32"); | ||
333 | assert_eq!(info.doc().map(|it| it.into()), Some("test".to_string())); | ||
334 | } | ||
335 | |||
336 | #[test] | ||
337 | fn test_fn_signature_with_docs() { | ||
338 | let info = call_info( | ||
339 | r#" | ||
340 | /// Adds one to the number given. | ||
341 | /// | ||
342 | /// # Examples | ||
343 | /// | ||
344 | /// ``` | ||
345 | /// let five = 5; | ||
346 | /// | ||
347 | /// assert_eq!(6, my_crate::add_one(5)); | ||
348 | /// ``` | ||
349 | pub fn add_one(x: i32) -> i32 { | ||
350 | x + 1 | ||
351 | } | ||
352 | |||
353 | pub fn do() { | ||
354 | add_one(<|> | ||
355 | }"#, | ||
356 | ); | ||
357 | |||
358 | assert_eq!(info.parameters(), ["x: i32"]); | ||
359 | assert_eq!(info.active_parameter, Some(0)); | ||
360 | assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32"); | ||
361 | assert_eq!( | ||
362 | info.doc().map(|it| it.into()), | ||
363 | Some( | ||
364 | r#"Adds one to the number given. | ||
365 | |||
366 | # Examples | ||
367 | |||
368 | ``` | ||
369 | let five = 5; | ||
370 | |||
371 | assert_eq!(6, my_crate::add_one(5)); | ||
372 | ```"# | ||
373 | .to_string() | ||
374 | ) | ||
375 | ); | ||
376 | } | ||
377 | |||
378 | #[test] | ||
379 | fn test_fn_signature_with_docs_impl() { | ||
380 | let info = call_info( | ||
381 | r#" | ||
382 | struct addr; | ||
383 | impl addr { | ||
384 | /// Adds one to the number given. | ||
385 | /// | ||
386 | /// # Examples | ||
387 | /// | ||
388 | /// ``` | ||
389 | /// let five = 5; | ||
390 | /// | ||
391 | /// assert_eq!(6, my_crate::add_one(5)); | ||
392 | /// ``` | ||
393 | pub fn add_one(x: i32) -> i32 { | ||
394 | x + 1 | ||
395 | } | ||
396 | } | ||
397 | |||
398 | pub fn do_it() { | ||
399 | addr {}; | ||
400 | addr::add_one(<|>); | ||
401 | }"#, | ||
402 | ); | ||
403 | |||
404 | assert_eq!(info.parameters(), ["x: i32"]); | ||
405 | assert_eq!(info.active_parameter, Some(0)); | ||
406 | assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32"); | ||
407 | assert_eq!( | ||
408 | info.doc().map(|it| it.into()), | ||
409 | Some( | ||
410 | r#"Adds one to the number given. | ||
411 | |||
412 | # Examples | ||
413 | |||
414 | ``` | ||
415 | let five = 5; | ||
416 | |||
417 | assert_eq!(6, my_crate::add_one(5)); | ||
418 | ```"# | ||
419 | .to_string() | ||
420 | ) | ||
421 | ); | ||
422 | } | ||
423 | |||
424 | #[test] | ||
425 | fn test_fn_signature_with_docs_from_actix() { | ||
426 | let info = call_info( | ||
427 | r#" | ||
428 | struct WriteHandler<E>; | ||
429 | |||
430 | impl<E> WriteHandler<E> { | ||
431 | /// Method is called when writer emits error. | ||
432 | /// | ||
433 | /// If this method returns `ErrorAction::Continue` writer processing | ||
434 | /// continues otherwise stream processing stops. | ||
435 | fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running { | ||
436 | Running::Stop | ||
437 | } | ||
438 | |||
439 | /// Method is called when writer finishes. | ||
440 | /// | ||
441 | /// By default this method stops actor's `Context`. | ||
442 | fn finished(&mut self, ctx: &mut Self::Context) { | ||
443 | ctx.stop() | ||
444 | } | ||
445 | } | ||
446 | |||
447 | pub fn foo(mut r: WriteHandler<()>) { | ||
448 | r.finished(<|>); | ||
449 | } | ||
450 | |||
451 | "#, | ||
452 | ); | ||
453 | |||
454 | assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string()); | ||
455 | assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]); | ||
456 | assert_eq!(info.active_parameter, Some(1)); | ||
457 | assert_eq!( | ||
458 | info.doc().map(|it| it.into()), | ||
459 | Some( | ||
460 | r#"Method is called when writer finishes. | ||
461 | |||
462 | By default this method stops actor's `Context`."# | ||
463 | .to_string() | ||
464 | ) | ||
465 | ); | ||
466 | } | ||
467 | |||
468 | #[test] | ||
469 | fn call_info_bad_offset() { | ||
470 | covers!(call_info_bad_offset); | ||
471 | let (analysis, position) = single_file_with_position( | ||
472 | r#"fn foo(x: u32, y: u32) -> u32 {x + y} | ||
473 | fn bar() { foo <|> (3, ); }"#, | ||
474 | ); | ||
475 | let call_info = analysis.call_info(position).unwrap(); | ||
476 | assert!(call_info.is_none()); | ||
477 | } | ||
478 | |||
479 | #[test] | ||
480 | fn test_nested_method_in_lamba() { | ||
481 | let info = call_info( | ||
482 | r#"struct Foo; | ||
483 | |||
484 | impl Foo { | ||
485 | fn bar(&self, _: u32) { } | ||
486 | } | ||
487 | |||
488 | fn bar(_: u32) { } | ||
489 | |||
490 | fn main() { | ||
491 | let foo = Foo; | ||
492 | std::thread::spawn(move || foo.bar(<|>)); | ||
493 | }"#, | ||
494 | ); | ||
495 | |||
496 | assert_eq!(info.parameters(), ["&self", "_: u32"]); | ||
497 | assert_eq!(info.active_parameter, Some(1)); | ||
498 | assert_eq!(info.label(), "fn bar(&self, _: u32)"); | ||
499 | } | ||
500 | |||
501 | #[test] | ||
502 | fn works_for_tuple_structs() { | ||
503 | let info = call_info( | ||
504 | r#" | ||
505 | /// A cool tuple struct | ||
506 | struct TS(u32, i32); | ||
507 | fn main() { | ||
508 | let s = TS(0, <|>); | ||
509 | }"#, | ||
510 | ); | ||
511 | |||
512 | assert_eq!(info.label(), "struct TS(u32, i32) -> TS"); | ||
513 | assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); | ||
514 | assert_eq!(info.active_parameter, Some(1)); | ||
515 | } | ||
516 | |||
517 | #[test] | ||
518 | #[should_panic] | ||
519 | fn cant_call_named_structs() { | ||
520 | let _ = call_info( | ||
521 | r#" | ||
522 | struct TS { x: u32, y: i32 } | ||
523 | fn main() { | ||
524 | let s = TS(<|>); | ||
525 | }"#, | ||
526 | ); | ||
527 | } | ||
528 | |||
529 | #[test] | ||
530 | fn works_for_enum_variants() { | ||
531 | let info = call_info( | ||
532 | r#" | ||
533 | enum E { | ||
534 | /// A Variant | ||
535 | A(i32), | ||
536 | /// Another | ||
537 | B, | ||
538 | /// And C | ||
539 | C { a: i32, b: i32 } | ||
540 | } | ||
541 | |||
542 | fn main() { | ||
543 | let a = E::A(<|>); | ||
544 | } | ||
545 | "#, | ||
546 | ); | ||
547 | |||
548 | assert_eq!(info.label(), "E::A(0: i32)"); | ||
549 | assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string())); | ||
550 | assert_eq!(info.active_parameter, Some(0)); | ||
551 | } | ||
552 | |||
553 | #[test] | ||
554 | #[should_panic] | ||
555 | fn cant_call_enum_records() { | ||
556 | let _ = call_info( | ||
557 | r#" | ||
558 | enum E { | ||
559 | /// A Variant | ||
560 | A(i32), | ||
561 | /// Another | ||
562 | B, | ||
563 | /// And C | ||
564 | C { a: i32, b: i32 } | ||
565 | } | ||
566 | |||
567 | fn main() { | ||
568 | let a = E::C(<|>); | ||
569 | } | ||
570 | "#, | ||
571 | ); | ||
572 | } | ||
573 | |||
574 | #[test] | ||
575 | fn fn_signature_for_macro() { | ||
576 | let info = call_info( | ||
577 | r#" | ||
578 | /// empty macro | ||
579 | macro_rules! foo { | ||
580 | () => {} | ||
581 | } | ||
582 | |||
583 | fn f() { | ||
584 | foo!(<|>); | ||
585 | } | ||
586 | "#, | ||
587 | ); | ||
588 | |||
589 | assert_eq!(info.label(), "foo!()"); | ||
590 | assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string())); | ||
591 | } | ||
592 | } | ||