aboutsummaryrefslogtreecommitdiff
path: root/crates/call_info
diff options
context:
space:
mode:
Diffstat (limited to 'crates/call_info')
-rw-r--r--crates/call_info/Cargo.toml26
-rw-r--r--crates/call_info/src/lib.rs755
2 files changed, 781 insertions, 0 deletions
diff --git a/crates/call_info/Cargo.toml b/crates/call_info/Cargo.toml
new file mode 100644
index 000000000..98c0bd6db
--- /dev/null
+++ b/crates/call_info/Cargo.toml
@@ -0,0 +1,26 @@
1[package]
2name = "call_info"
3version = "0.0.0"
4description = "TBD"
5license = "MIT OR Apache-2.0"
6authors = ["rust-analyzer developers"]
7edition = "2018"
8
9[lib]
10doctest = false
11
12[dependencies]
13either = "1.5.3"
14
15stdx = { path = "../stdx", version = "0.0.0" }
16syntax = { path = "../syntax", version = "0.0.0" }
17base_db = { path = "../base_db", version = "0.0.0" }
18ide_db = { path = "../ide_db", version = "0.0.0" }
19test_utils = { path = "../test_utils", version = "0.0.0" }
20
21# call_info crate should depend only on the top-level `hir` package. if you need
22# something from some `hir_xxx` subpackage, reexport the API via `hir`.
23hir = { path = "../hir", version = "0.0.0" }
24
25[dev-dependencies]
26expect-test = "1.0"
diff --git a/crates/call_info/src/lib.rs b/crates/call_info/src/lib.rs
new file mode 100644
index 000000000..c45406c25
--- /dev/null
+++ b/crates/call_info/src/lib.rs
@@ -0,0 +1,755 @@
1//! This crate provides primitives for tracking the information about a call site.
2use base_db::FilePosition;
3use either::Either;
4use hir::{HasAttrs, HirDisplay, Semantics, Type};
5use ide_db::RootDatabase;
6use stdx::format_to;
7use syntax::{
8 ast::{self, ArgListOwner},
9 match_ast, AstNode, SyntaxNode, SyntaxToken, TextRange, TextSize,
10};
11use test_utils::mark;
12
13/// Contains information about a call site. Specifically the
14/// `FunctionSignature`and current parameter.
15#[derive(Debug)]
16pub struct CallInfo {
17 pub doc: Option<String>,
18 pub signature: String,
19 pub active_parameter: Option<usize>,
20 parameters: Vec<TextRange>,
21}
22
23impl CallInfo {
24 pub fn parameter_labels(&self) -> impl Iterator<Item = &str> + '_ {
25 self.parameters.iter().map(move |&it| &self.signature[it])
26 }
27 pub fn parameter_ranges(&self) -> &[TextRange] {
28 &self.parameters
29 }
30 fn push_param(&mut self, param: &str) {
31 if !self.signature.ends_with('(') {
32 self.signature.push_str(", ");
33 }
34 let start = TextSize::of(&self.signature);
35 self.signature.push_str(param);
36 let end = TextSize::of(&self.signature);
37 self.parameters.push(TextRange::new(start, end))
38 }
39}
40
41/// Computes parameter information for the given call expression.
42pub fn call_info(db: &RootDatabase, position: FilePosition) -> Option<CallInfo> {
43 let sema = Semantics::new(db);
44 let file = sema.parse(position.file_id);
45 let file = file.syntax();
46 let token = file.token_at_offset(position.offset).next()?;
47 let token = sema.descend_into_macros(token);
48
49 let (callable, active_parameter) = call_info_impl(&sema, token)?;
50
51 let mut res =
52 CallInfo { doc: None, signature: String::new(), parameters: vec![], active_parameter };
53
54 match callable.kind() {
55 hir::CallableKind::Function(func) => {
56 res.doc = func.docs(db).map(|it| it.as_str().to_string());
57 format_to!(res.signature, "fn {}", func.name(db));
58 }
59 hir::CallableKind::TupleStruct(strukt) => {
60 res.doc = strukt.docs(db).map(|it| it.as_str().to_string());
61 format_to!(res.signature, "struct {}", strukt.name(db));
62 }
63 hir::CallableKind::TupleEnumVariant(variant) => {
64 res.doc = variant.docs(db).map(|it| it.as_str().to_string());
65 format_to!(
66 res.signature,
67 "enum {}::{}",
68 variant.parent_enum(db).name(db),
69 variant.name(db)
70 );
71 }
72 hir::CallableKind::Closure => (),
73 }
74
75 res.signature.push('(');
76 {
77 if let Some(self_param) = callable.receiver_param(db) {
78 format_to!(res.signature, "{}", self_param)
79 }
80 let mut buf = String::new();
81 for (pat, ty) in callable.params(db) {
82 buf.clear();
83 if let Some(pat) = pat {
84 match pat {
85 Either::Left(_self) => format_to!(buf, "self: "),
86 Either::Right(pat) => format_to!(buf, "{}: ", pat),
87 }
88 }
89 format_to!(buf, "{}", ty.display(db));
90 res.push_param(&buf);
91 }
92 }
93 res.signature.push(')');
94
95 match callable.kind() {
96 hir::CallableKind::Function(_) | hir::CallableKind::Closure => {
97 let ret_type = callable.return_type();
98 if !ret_type.is_unit() {
99 format_to!(res.signature, " -> {}", ret_type.display(db));
100 }
101 }
102 hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
103 }
104 Some(res)
105}
106
107fn call_info_impl(
108 sema: &Semantics<RootDatabase>,
109 token: SyntaxToken,
110) -> Option<(hir::Callable, Option<usize>)> {
111 // Find the calling expression and it's NameRef
112 let calling_node = FnCallNode::with_node(&token.parent())?;
113
114 let callable = match &calling_node {
115 FnCallNode::CallExpr(call) => sema.type_of_expr(&call.expr()?)?.as_callable(sema.db)?,
116 FnCallNode::MethodCallExpr(call) => sema.resolve_method_call_as_callable(call)?,
117 };
118 let active_param = if let Some(arg_list) = calling_node.arg_list() {
119 // Number of arguments specified at the call site
120 let num_args_at_callsite = arg_list.args().count();
121
122 let arg_list_range = arg_list.syntax().text_range();
123 if !arg_list_range.contains_inclusive(token.text_range().start()) {
124 mark::hit!(call_info_bad_offset);
125 return None;
126 }
127 let param = std::cmp::min(
128 num_args_at_callsite,
129 arg_list
130 .args()
131 .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
132 .count(),
133 );
134
135 Some(param)
136 } else {
137 None
138 };
139 Some((callable, active_param))
140}
141
142#[derive(Debug)]
143pub struct ActiveParameter {
144 pub ty: Type,
145 pub name: String,
146}
147
148impl ActiveParameter {
149 pub fn at(db: &RootDatabase, position: FilePosition) -> Option<Self> {
150 let sema = Semantics::new(db);
151 let file = sema.parse(position.file_id);
152 let file = file.syntax();
153 let token = file.token_at_offset(position.offset).next()?;
154 let token = sema.descend_into_macros(token);
155 Self::at_token(&sema, token)
156 }
157
158 pub fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> {
159 let (signature, active_parameter) = call_info_impl(&sema, token)?;
160
161 let idx = active_parameter?;
162 let mut params = signature.params(sema.db);
163 if !(idx < params.len()) {
164 mark::hit!(too_many_arguments);
165 return None;
166 }
167 let (pat, ty) = params.swap_remove(idx);
168 let name = pat?.to_string();
169 Some(ActiveParameter { ty, name })
170 }
171}
172
173#[derive(Debug)]
174pub enum FnCallNode {
175 CallExpr(ast::CallExpr),
176 MethodCallExpr(ast::MethodCallExpr),
177}
178
179impl FnCallNode {
180 fn with_node(syntax: &SyntaxNode) -> Option<FnCallNode> {
181 syntax.ancestors().find_map(|node| {
182 match_ast! {
183 match node {
184 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
185 ast::MethodCallExpr(it) => {
186 let arg_list = it.arg_list()?;
187 if !arg_list.syntax().text_range().contains_range(syntax.text_range()) {
188 return None;
189 }
190 Some(FnCallNode::MethodCallExpr(it))
191 },
192 _ => None,
193 }
194 }
195 })
196 }
197
198 pub fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> {
199 match_ast! {
200 match node {
201 ast::CallExpr(it) => Some(FnCallNode::CallExpr(it)),
202 ast::MethodCallExpr(it) => Some(FnCallNode::MethodCallExpr(it)),
203 _ => None,
204 }
205 }
206 }
207
208 pub fn name_ref(&self) -> Option<ast::NameRef> {
209 match self {
210 FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
211 ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
212 _ => return None,
213 }),
214
215 FnCallNode::MethodCallExpr(call_expr) => {
216 call_expr.syntax().children().filter_map(ast::NameRef::cast).next()
217 }
218 }
219 }
220
221 fn arg_list(&self) -> Option<ast::ArgList> {
222 match self {
223 FnCallNode::CallExpr(expr) => expr.arg_list(),
224 FnCallNode::MethodCallExpr(expr) => expr.arg_list(),
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use base_db::{fixture::ChangeFixture, FilePosition};
232 use expect_test::{expect, Expect};
233 use ide_db::RootDatabase;
234 use test_utils::{mark, RangeOrOffset};
235
236 /// Creates analysis from a multi-file fixture, returns positions marked with <|>.
237 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
238 let change_fixture = ChangeFixture::parse(ra_fixture);
239 let mut database = RootDatabase::default();
240 database.apply_change(change_fixture.change);
241 let (file_id, range_or_offset) =
242 change_fixture.file_position.expect("expected a marker (<|>)");
243 let offset = match range_or_offset {
244 RangeOrOffset::Range(_) => panic!(),
245 RangeOrOffset::Offset(it) => it,
246 };
247 (database, FilePosition { file_id, offset })
248 }
249
250 fn check(ra_fixture: &str, expect: Expect) {
251 let (db, position) = position(ra_fixture);
252 let call_info = crate::call_info(&db, position);
253 let actual = match call_info {
254 Some(call_info) => {
255 let docs = match &call_info.doc {
256 None => "".to_string(),
257 Some(docs) => format!("{}\n------\n", docs.as_str()),
258 };
259 let params = call_info
260 .parameter_labels()
261 .enumerate()
262 .map(|(i, param)| {
263 if Some(i) == call_info.active_parameter {
264 format!("<{}>", param)
265 } else {
266 param.to_string()
267 }
268 })
269 .collect::<Vec<_>>()
270 .join(", ");
271 format!("{}{}\n({})\n", docs, call_info.signature, params)
272 }
273 None => String::new(),
274 };
275 expect.assert_eq(&actual);
276 }
277
278 #[test]
279 fn test_fn_signature_two_args() {
280 check(
281 r#"
282fn foo(x: u32, y: u32) -> u32 {x + y}
283fn bar() { foo(<|>3, ); }
284"#,
285 expect![[r#"
286 fn foo(x: u32, y: u32) -> u32
287 (<x: u32>, y: u32)
288 "#]],
289 );
290 check(
291 r#"
292fn foo(x: u32, y: u32) -> u32 {x + y}
293fn bar() { foo(3<|>, ); }
294"#,
295 expect![[r#"
296 fn foo(x: u32, y: u32) -> u32
297 (<x: u32>, y: u32)
298 "#]],
299 );
300 check(
301 r#"
302fn foo(x: u32, y: u32) -> u32 {x + y}
303fn bar() { foo(3,<|> ); }
304"#,
305 expect![[r#"
306 fn foo(x: u32, y: u32) -> u32
307 (x: u32, <y: u32>)
308 "#]],
309 );
310 check(
311 r#"
312fn foo(x: u32, y: u32) -> u32 {x + y}
313fn bar() { foo(3, <|>); }
314"#,
315 expect![[r#"
316 fn foo(x: u32, y: u32) -> u32
317 (x: u32, <y: u32>)
318 "#]],
319 );
320 }
321
322 #[test]
323 fn test_fn_signature_two_args_empty() {
324 check(
325 r#"
326fn foo(x: u32, y: u32) -> u32 {x + y}
327fn bar() { foo(<|>); }
328"#,
329 expect![[r#"
330 fn foo(x: u32, y: u32) -> u32
331 (<x: u32>, y: u32)
332 "#]],
333 );
334 }
335
336 #[test]
337 fn test_fn_signature_two_args_first_generics() {
338 check(
339 r#"
340fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
341 where T: Copy + Display, U: Debug
342{ x + y }
343
344fn bar() { foo(<|>3, ); }
345"#,
346 expect![[r#"
347 fn foo(x: i32, y: {unknown}) -> u32
348 (<x: i32>, y: {unknown})
349 "#]],
350 );
351 }
352
353 #[test]
354 fn test_fn_signature_no_params() {
355 check(
356 r#"
357fn foo<T>() -> T where T: Copy + Display {}
358fn bar() { foo(<|>); }
359"#,
360 expect![[r#"
361 fn foo() -> {unknown}
362 ()
363 "#]],
364 );
365 }
366
367 #[test]
368 fn test_fn_signature_for_impl() {
369 check(
370 r#"
371struct F;
372impl F { pub fn new() { } }
373fn bar() {
374 let _ : F = F::new(<|>);
375}
376"#,
377 expect![[r#"
378 fn new()
379 ()
380 "#]],
381 );
382 }
383
384 #[test]
385 fn test_fn_signature_for_method_self() {
386 check(
387 r#"
388struct S;
389impl S { pub fn do_it(&self) {} }
390
391fn bar() {
392 let s: S = S;
393 s.do_it(<|>);
394}
395"#,
396 expect![[r#"
397 fn do_it(&self)
398 ()
399 "#]],
400 );
401 }
402
403 #[test]
404 fn test_fn_signature_for_method_with_arg() {
405 check(
406 r#"
407struct S;
408impl S {
409 fn foo(&self, x: i32) {}
410}
411
412fn main() { S.foo(<|>); }
413"#,
414 expect![[r#"
415 fn foo(&self, x: i32)
416 (<x: i32>)
417 "#]],
418 );
419 }
420
421 #[test]
422 fn test_fn_signature_for_method_with_arg_as_assoc_fn() {
423 check(
424 r#"
425struct S;
426impl S {
427 fn foo(&self, x: i32) {}
428}
429
430fn main() { S::foo(<|>); }
431"#,
432 expect![[r#"
433 fn foo(self: &S, x: i32)
434 (<self: &S>, x: i32)
435 "#]],
436 );
437 }
438
439 #[test]
440 fn test_fn_signature_with_docs_simple() {
441 check(
442 r#"
443/// test
444// non-doc-comment
445fn foo(j: u32) -> u32 {
446 j
447}
448
449fn bar() {
450 let _ = foo(<|>);
451}
452"#,
453 expect![[r#"
454 test
455 ------
456 fn foo(j: u32) -> u32
457 (<j: u32>)
458 "#]],
459 );
460 }
461
462 #[test]
463 fn test_fn_signature_with_docs() {
464 check(
465 r#"
466/// Adds one to the number given.
467///
468/// # Examples
469///
470/// ```
471/// let five = 5;
472///
473/// assert_eq!(6, my_crate::add_one(5));
474/// ```
475pub fn add_one(x: i32) -> i32 {
476 x + 1
477}
478
479pub fn do() {
480 add_one(<|>
481}"#,
482 expect![[r##"
483 Adds one to the number given.
484
485 # Examples
486
487 ```
488 let five = 5;
489
490 assert_eq!(6, my_crate::add_one(5));
491 ```
492 ------
493 fn add_one(x: i32) -> i32
494 (<x: i32>)
495 "##]],
496 );
497 }
498
499 #[test]
500 fn test_fn_signature_with_docs_impl() {
501 check(
502 r#"
503struct addr;
504impl addr {
505 /// Adds one to the number given.
506 ///
507 /// # Examples
508 ///
509 /// ```
510 /// let five = 5;
511 ///
512 /// assert_eq!(6, my_crate::add_one(5));
513 /// ```
514 pub fn add_one(x: i32) -> i32 {
515 x + 1
516 }
517}
518
519pub fn do_it() {
520 addr {};
521 addr::add_one(<|>);
522}
523"#,
524 expect![[r##"
525 Adds one to the number given.
526
527 # Examples
528
529 ```
530 let five = 5;
531
532 assert_eq!(6, my_crate::add_one(5));
533 ```
534 ------
535 fn add_one(x: i32) -> i32
536 (<x: i32>)
537 "##]],
538 );
539 }
540
541 #[test]
542 fn test_fn_signature_with_docs_from_actix() {
543 check(
544 r#"
545struct WriteHandler<E>;
546
547impl<E> WriteHandler<E> {
548 /// Method is called when writer emits error.
549 ///
550 /// If this method returns `ErrorAction::Continue` writer processing
551 /// continues otherwise stream processing stops.
552 fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running {
553 Running::Stop
554 }
555
556 /// Method is called when writer finishes.
557 ///
558 /// By default this method stops actor's `Context`.
559 fn finished(&mut self, ctx: &mut Self::Context) {
560 ctx.stop()
561 }
562}
563
564pub fn foo(mut r: WriteHandler<()>) {
565 r.finished(<|>);
566}
567"#,
568 expect![[r#"
569 Method is called when writer finishes.
570
571 By default this method stops actor's `Context`.
572 ------
573 fn finished(&mut self, ctx: &mut {unknown})
574 (<ctx: &mut {unknown}>)
575 "#]],
576 );
577 }
578
579 #[test]
580 fn call_info_bad_offset() {
581 mark::check!(call_info_bad_offset);
582 check(
583 r#"
584fn foo(x: u32, y: u32) -> u32 {x + y}
585fn bar() { foo <|> (3, ); }
586"#,
587 expect![[""]],
588 );
589 }
590
591 #[test]
592 fn test_nested_method_in_lambda() {
593 check(
594 r#"
595struct Foo;
596impl Foo { fn bar(&self, _: u32) { } }
597
598fn bar(_: u32) { }
599
600fn main() {
601 let foo = Foo;
602 std::thread::spawn(move || foo.bar(<|>));
603}
604"#,
605 expect![[r#"
606 fn bar(&self, _: u32)
607 (<_: u32>)
608 "#]],
609 );
610 }
611
612 #[test]
613 fn works_for_tuple_structs() {
614 check(
615 r#"
616/// A cool tuple struct
617struct S(u32, i32);
618fn main() {
619 let s = S(0, <|>);
620}
621"#,
622 expect![[r#"
623 A cool tuple struct
624 ------
625 struct S(u32, i32)
626 (u32, <i32>)
627 "#]],
628 );
629 }
630
631 #[test]
632 fn generic_struct() {
633 check(
634 r#"
635struct S<T>(T);
636fn main() {
637 let s = S(<|>);
638}
639"#,
640 expect![[r#"
641 struct S({unknown})
642 (<{unknown}>)
643 "#]],
644 );
645 }
646
647 #[test]
648 fn works_for_enum_variants() {
649 check(
650 r#"
651enum E {
652 /// A Variant
653 A(i32),
654 /// Another
655 B,
656 /// And C
657 C { a: i32, b: i32 }
658}
659
660fn main() {
661 let a = E::A(<|>);
662}
663"#,
664 expect![[r#"
665 A Variant
666 ------
667 enum E::A(i32)
668 (<i32>)
669 "#]],
670 );
671 }
672
673 #[test]
674 fn cant_call_struct_record() {
675 check(
676 r#"
677struct S { x: u32, y: i32 }
678fn main() {
679 let s = S(<|>);
680}
681"#,
682 expect![[""]],
683 );
684 }
685
686 #[test]
687 fn cant_call_enum_record() {
688 check(
689 r#"
690enum E {
691 /// A Variant
692 A(i32),
693 /// Another
694 B,
695 /// And C
696 C { a: i32, b: i32 }
697}
698
699fn main() {
700 let a = E::C(<|>);
701}
702"#,
703 expect![[""]],
704 );
705 }
706
707 #[test]
708 fn fn_signature_for_call_in_macro() {
709 check(
710 r#"
711macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
712fn foo() { }
713id! {
714 fn bar() { foo(<|>); }
715}
716"#,
717 expect![[r#"
718 fn foo()
719 ()
720 "#]],
721 );
722 }
723
724 #[test]
725 fn call_info_for_lambdas() {
726 check(
727 r#"
728struct S;
729fn foo(s: S) -> i32 { 92 }
730fn main() {
731 (|s| foo(s))(<|>)
732}
733 "#,
734 expect![[r#"
735 (S) -> i32
736 (<S>)
737 "#]],
738 )
739 }
740
741 #[test]
742 fn call_info_for_fn_ptr() {
743 check(
744 r#"
745fn main(f: fn(i32, f64) -> char) {
746 f(0, <|>)
747}
748 "#,
749 expect![[r#"
750 (i32, f64) -> char
751 (i32, <f64>)
752 "#]],
753 )
754 }
755}