aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/call_info.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/call_info.rs')
-rw-r--r--crates/ra_ide/src/call_info.rs592
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
3use ra_db::SourceDatabase;
4use ra_syntax::{
5 algo::ancestors_at_offset,
6 ast::{self, ArgListOwner},
7 match_ast, AstNode, SyntaxNode, TextUnit,
8};
9use test_utils::tested_by;
10
11use crate::{db::RootDatabase, CallInfo, FilePosition, FunctionSignature};
12
13/// Computes parameter information for the given call expression.
14pub(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)]
89enum FnCallNode {
90 CallExpr(ast::CallExpr),
91 MethodCallExpr(ast::MethodCallExpr),
92 MacroCallExpr(ast::MacroCall),
93}
94
95impl 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
133impl 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)]
164mod 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}
191fn 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}
202fn 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}
213fn 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}
224fn bar() { foo(<|>3, ); }"#,
225 );
226
227 assert_eq!(info.parameters(), ["x: T", "y: U"]);
228 assert_eq!(
229 info.label(),
230 r#"
231fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
232where 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 {}
244fn bar() { foo(<|>); }"#,
245 );
246
247 assert!(info.parameters().is_empty());
248 assert_eq!(
249 info.label(),
250 r#"
251fn foo<T>() -> T
252where 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{}} }
263fn 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;
274impl F {
275 pub fn new() -> F{
276 F{}
277 }
278
279 pub fn do_it(&self) {}
280}
281
282fn 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;
296impl F {
297 pub fn new() -> F{
298 F{}
299 }
300
301 pub fn do_it(&self, x: i32) {}
302}
303
304fn 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
320fn foo(j: u32) -> u32 {
321 j
322}
323
324fn 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/// ```
349pub fn add_one(x: i32) -> i32 {
350 x + 1
351}
352
353pub 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```
369let five = 5;
370
371assert_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#"
382struct addr;
383impl 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
398pub 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```
415let five = 5;
416
417assert_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#"
428struct WriteHandler<E>;
429
430impl<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
447pub 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
462By 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
484impl Foo {
485 fn bar(&self, _: u32) { }
486}
487
488fn bar(_: u32) { }
489
490fn 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
506struct TS(u32, i32);
507fn 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#"
522struct TS { x: u32, y: i32 }
523fn main() {
524 let s = TS(<|>);
525}"#,
526 );
527 }
528
529 #[test]
530 fn works_for_enum_variants() {
531 let info = call_info(
532 r#"
533enum E {
534 /// A Variant
535 A(i32),
536 /// Another
537 B,
538 /// And C
539 C { a: i32, b: i32 }
540}
541
542fn 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#"
558enum E {
559 /// A Variant
560 A(i32),
561 /// Another
562 B,
563 /// And C
564 C { a: i32, b: i32 }
565}
566
567fn 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
579macro_rules! foo {
580 () => {}
581}
582
583fn 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}