diff options
Diffstat (limited to 'crates/ra_analysis/src/call_info.rs')
-rw-r--r-- | crates/ra_analysis/src/call_info.rs | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/call_info.rs b/crates/ra_analysis/src/call_info.rs new file mode 100644 index 000000000..a31a54ef6 --- /dev/null +++ b/crates/ra_analysis/src/call_info.rs | |||
@@ -0,0 +1,119 @@ | |||
1 | use ra_db::{SyntaxDatabase, Cancelable}; | ||
2 | use ra_syntax::{ | ||
3 | AstNode, SyntaxNode, TextUnit, TextRange, | ||
4 | SyntaxKind::FN_DEF, | ||
5 | ast::{self, ArgListOwner}, | ||
6 | }; | ||
7 | use ra_editor::find_node_at_offset; | ||
8 | use hir::FnSignatureInfo; | ||
9 | |||
10 | use crate::{FilePosition, db::RootDatabase}; | ||
11 | |||
12 | /// Computes parameter information for the given call expression. | ||
13 | pub(crate) fn call_info( | ||
14 | db: &RootDatabase, | ||
15 | position: FilePosition, | ||
16 | ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { | ||
17 | let file = db.source_file(position.file_id); | ||
18 | let syntax = file.syntax(); | ||
19 | |||
20 | // Find the calling expression and it's NameRef | ||
21 | let calling_node = ctry!(FnCallNode::with_node(syntax, position.offset)); | ||
22 | let name_ref = ctry!(calling_node.name_ref()); | ||
23 | |||
24 | // Resolve the function's NameRef (NOTE: this isn't entirely accurate). | ||
25 | let file_symbols = db.index_resolve(name_ref)?; | ||
26 | for symbol in file_symbols { | ||
27 | if symbol.ptr.kind() == FN_DEF { | ||
28 | let fn_file = db.source_file(symbol.file_id); | ||
29 | let fn_def = symbol.ptr.resolve(&fn_file); | ||
30 | let fn_def = ast::FnDef::cast(&fn_def).unwrap(); | ||
31 | let descr = ctry!(hir::source_binder::function_from_source( | ||
32 | db, | ||
33 | symbol.file_id, | ||
34 | fn_def | ||
35 | )?); | ||
36 | if let Some(descriptor) = descr.signature_info(db) { | ||
37 | // If we have a calling expression let's find which argument we are on | ||
38 | let mut current_parameter = None; | ||
39 | |||
40 | let num_params = descriptor.params.len(); | ||
41 | let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); | ||
42 | |||
43 | if num_params == 1 { | ||
44 | if !has_self { | ||
45 | current_parameter = Some(0); | ||
46 | } | ||
47 | } else if num_params > 1 { | ||
48 | // Count how many parameters into the call we are. | ||
49 | // TODO: This is best effort for now and should be fixed at some point. | ||
50 | // It may be better to see where we are in the arg_list and then check | ||
51 | // where offset is in that list (or beyond). | ||
52 | // Revisit this after we get documentation comments in. | ||
53 | if let Some(ref arg_list) = calling_node.arg_list() { | ||
54 | let start = arg_list.syntax().range().start(); | ||
55 | |||
56 | let range_search = TextRange::from_to(start, position.offset); | ||
57 | let mut commas: usize = arg_list | ||
58 | .syntax() | ||
59 | .text() | ||
60 | .slice(range_search) | ||
61 | .to_string() | ||
62 | .matches(',') | ||
63 | .count(); | ||
64 | |||
65 | // If we have a method call eat the first param since it's just self. | ||
66 | if has_self { | ||
67 | commas += 1; | ||
68 | } | ||
69 | |||
70 | current_parameter = Some(commas); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | return Ok(Some((descriptor, current_parameter))); | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | |||
79 | Ok(None) | ||
80 | } | ||
81 | |||
82 | enum FnCallNode<'a> { | ||
83 | CallExpr(&'a ast::CallExpr), | ||
84 | MethodCallExpr(&'a ast::MethodCallExpr), | ||
85 | } | ||
86 | |||
87 | impl<'a> FnCallNode<'a> { | ||
88 | pub fn with_node(syntax: &'a SyntaxNode, offset: TextUnit) -> Option<FnCallNode<'a>> { | ||
89 | if let Some(expr) = find_node_at_offset::<ast::CallExpr>(syntax, offset) { | ||
90 | return Some(FnCallNode::CallExpr(expr)); | ||
91 | } | ||
92 | if let Some(expr) = find_node_at_offset::<ast::MethodCallExpr>(syntax, offset) { | ||
93 | return Some(FnCallNode::MethodCallExpr(expr)); | ||
94 | } | ||
95 | None | ||
96 | } | ||
97 | |||
98 | pub fn name_ref(&self) -> Option<&'a ast::NameRef> { | ||
99 | match *self { | ||
100 | FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()?.kind() { | ||
101 | ast::ExprKind::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, | ||
102 | _ => return None, | ||
103 | }), | ||
104 | |||
105 | FnCallNode::MethodCallExpr(call_expr) => call_expr | ||
106 | .syntax() | ||
107 | .children() | ||
108 | .filter_map(ast::NameRef::cast) | ||
109 | .nth(0), | ||
110 | } | ||
111 | } | ||
112 | |||
113 | pub fn arg_list(&self) -> Option<&'a ast::ArgList> { | ||
114 | match *self { | ||
115 | FnCallNode::CallExpr(expr) => expr.arg_list(), | ||
116 | FnCallNode::MethodCallExpr(expr) => expr.arg_list(), | ||
117 | } | ||
118 | } | ||
119 | } | ||