aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/descriptors/function
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/descriptors/function')
-rw-r--r--crates/ra_analysis/src/descriptors/function/imp.rs26
-rw-r--r--crates/ra_analysis/src/descriptors/function/mod.rs83
-rw-r--r--crates/ra_analysis/src/descriptors/function/scope.rs433
3 files changed, 542 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/descriptors/function/imp.rs b/crates/ra_analysis/src/descriptors/function/imp.rs
new file mode 100644
index 000000000..0a006f733
--- /dev/null
+++ b/crates/ra_analysis/src/descriptors/function/imp.rs
@@ -0,0 +1,26 @@
1use std::sync::Arc;
2
3use ra_syntax::{
4 ast::{AstNode, FnDef, FnDefNode},
5};
6
7use crate::{
8 descriptors::{
9 DescriptorDatabase,
10 function::{FnId, FnScopes},
11 },
12};
13
14/// Resolve `FnId` to the corresponding `SyntaxNode`
15/// TODO: this should return something more type-safe then `SyntaxNode`
16pub(crate) fn fn_syntax(db: &impl DescriptorDatabase, fn_id: FnId) -> FnDefNode {
17 let syntax = db.resolve_syntax_ptr(fn_id.0);
18 let fn_def = FnDef::cast(syntax.borrowed()).unwrap();
19 FnDefNode::new(fn_def)
20}
21
22pub(crate) fn fn_scopes(db: &impl DescriptorDatabase, fn_id: FnId) -> Arc<FnScopes> {
23 let syntax = db.fn_syntax(fn_id);
24 let res = FnScopes::new(syntax.ast());
25 Arc::new(res)
26}
diff --git a/crates/ra_analysis/src/descriptors/function/mod.rs b/crates/ra_analysis/src/descriptors/function/mod.rs
new file mode 100644
index 000000000..bb68b0ce7
--- /dev/null
+++ b/crates/ra_analysis/src/descriptors/function/mod.rs
@@ -0,0 +1,83 @@
1pub(super) mod imp;
2mod scope;
3
4use ra_syntax::{
5 ast::{self, AstNode, NameOwner}
6};
7
8use crate::{
9 FileId,
10 syntax_ptr::SyntaxPtr
11};
12
13pub(crate) use self::scope::{FnScopes, resolve_local_name};
14
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub(crate) struct FnId(SyntaxPtr);
18
19impl FnId {
20 pub(crate) fn new(file_id: FileId, fn_def: ast::FnDef) -> FnId {
21 let ptr = SyntaxPtr::new(file_id, fn_def.syntax());
22 FnId(ptr)
23 }
24}
25
26
27#[derive(Debug, Clone)]
28pub struct FnDescriptor {
29 pub name: String,
30 pub label: String,
31 pub ret_type: Option<String>,
32 pub params: Vec<String>,
33}
34
35impl FnDescriptor {
36 pub fn new(node: ast::FnDef) -> Option<Self> {
37 let name = node.name()?.text().to_string();
38
39 // Strip the body out for the label.
40 let label: String = if let Some(body) = node.body() {
41 let body_range = body.syntax().range();
42 let label: String = node
43 .syntax()
44 .children()
45 .filter(|child| !child.range().is_subrange(&body_range))
46 .map(|node| node.text().to_string())
47 .collect();
48 label
49 } else {
50 node.syntax().text().to_string()
51 };
52
53 let params = FnDescriptor::param_list(node);
54 let ret_type = node.ret_type().map(|r| r.syntax().text().to_string());
55
56 Some(FnDescriptor {
57 name,
58 ret_type,
59 params,
60 label,
61 })
62 }
63
64 fn param_list(node: ast::FnDef) -> Vec<String> {
65 let mut res = vec![];
66 if let Some(param_list) = node.param_list() {
67 if let Some(self_param) = param_list.self_param() {
68 res.push(self_param.syntax().text().to_string())
69 }
70
71 // Maybe use param.pat here? See if we can just extract the name?
72 //res.extend(param_list.params().map(|p| p.syntax().text().to_string()));
73 res.extend(
74 param_list
75 .params()
76 .filter_map(|p| p.pat())
77 .map(|pat| pat.syntax().text().to_string()),
78 );
79 }
80 res
81 }
82}
83
diff --git a/crates/ra_analysis/src/descriptors/function/scope.rs b/crates/ra_analysis/src/descriptors/function/scope.rs
new file mode 100644
index 000000000..d9929414c
--- /dev/null
+++ b/crates/ra_analysis/src/descriptors/function/scope.rs
@@ -0,0 +1,433 @@
1use rustc_hash::{FxHashMap, FxHashSet};
2
3use ra_syntax::{
4 algo::generate,
5 ast::{self, ArgListOwner, LoopBodyOwner, NameOwner},
6 AstNode, SmolStr, SyntaxNodeRef,
7};
8
9use crate::syntax_ptr::LocalSyntaxPtr;
10
11#[derive(Clone, Copy, PartialEq, Eq, Debug)]
12pub(crate) struct ScopeId(u32);
13
14#[derive(Debug, PartialEq, Eq)]
15pub struct FnScopes {
16 pub(crate) self_param: Option<LocalSyntaxPtr>,
17 scopes: Vec<ScopeData>,
18 scope_for: FxHashMap<LocalSyntaxPtr, ScopeId>,
19}
20
21#[derive(Debug, PartialEq, Eq)]
22pub struct ScopeEntry {
23 name: SmolStr,
24 ptr: LocalSyntaxPtr,
25}
26
27#[derive(Debug, PartialEq, Eq)]
28struct ScopeData {
29 parent: Option<ScopeId>,
30 entries: Vec<ScopeEntry>,
31}
32
33impl FnScopes {
34 pub(crate) fn new(fn_def: ast::FnDef) -> FnScopes {
35 let mut scopes = FnScopes {
36 self_param: fn_def
37 .param_list()
38 .and_then(|it| it.self_param())
39 .map(|it| LocalSyntaxPtr::new(it.syntax())),
40 scopes: Vec::new(),
41 scope_for: FxHashMap::default(),
42 };
43 let root = scopes.root_scope();
44 scopes.add_params_bindings(root, fn_def.param_list());
45 if let Some(body) = fn_def.body() {
46 compute_block_scopes(body, &mut scopes, root)
47 }
48 scopes
49 }
50 pub(crate) fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
51 &self.get(scope).entries
52 }
53 pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator<Item = ScopeId> + 'a {
54 generate(self.scope_for(node), move |&scope| {
55 self.get(scope).parent
56 })
57 }
58 fn root_scope(&mut self) -> ScopeId {
59 let res = ScopeId(self.scopes.len() as u32);
60 self.scopes.push(ScopeData {
61 parent: None,
62 entries: vec![],
63 });
64 res
65 }
66 fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
67 let res = ScopeId(self.scopes.len() as u32);
68 self.scopes.push(ScopeData {
69 parent: Some(parent),
70 entries: vec![],
71 });
72 res
73 }
74 fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) {
75 let entries = pat
76 .syntax()
77 .descendants()
78 .filter_map(ast::BindPat::cast)
79 .filter_map(ScopeEntry::new);
80 self.get_mut(scope).entries.extend(entries);
81 }
82 fn add_params_bindings(&mut self, scope: ScopeId, params: Option<ast::ParamList>) {
83 params
84 .into_iter()
85 .flat_map(|it| it.params())
86 .filter_map(|it| it.pat())
87 .for_each(|it| self.add_bindings(scope, it));
88 }
89 fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) {
90 self.scope_for.insert(LocalSyntaxPtr::new(node), scope);
91 }
92 fn scope_for(&self, node: SyntaxNodeRef) -> Option<ScopeId> {
93 node.ancestors()
94 .map(LocalSyntaxPtr::new)
95 .filter_map(|it| self.scope_for.get(&it).map(|&scope| scope))
96 .next()
97 }
98 fn get(&self, scope: ScopeId) -> &ScopeData {
99 &self.scopes[scope.0 as usize]
100 }
101 fn get_mut(&mut self, scope: ScopeId) -> &mut ScopeData {
102 &mut self.scopes[scope.0 as usize]
103 }
104}
105
106impl ScopeEntry {
107 fn new(pat: ast::BindPat) -> Option<ScopeEntry> {
108 let name = pat.name()?;
109 let res = ScopeEntry {
110 name: name.text(),
111 ptr: LocalSyntaxPtr::new(pat.syntax()),
112 };
113 Some(res)
114 }
115 pub(crate) fn name(&self) -> &SmolStr {
116 &self.name
117 }
118 pub(crate) fn ptr(&self) -> LocalSyntaxPtr {
119 self.ptr
120 }
121}
122
123fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) {
124 for stmt in block.statements() {
125 match stmt {
126 ast::Stmt::LetStmt(stmt) => {
127 if let Some(expr) = stmt.initializer() {
128 scopes.set_scope(expr.syntax(), scope);
129 compute_expr_scopes(expr, scopes, scope);
130 }
131 scope = scopes.new_scope(scope);
132 if let Some(pat) = stmt.pat() {
133 scopes.add_bindings(scope, pat);
134 }
135 }
136 ast::Stmt::ExprStmt(expr_stmt) => {
137 if let Some(expr) = expr_stmt.expr() {
138 scopes.set_scope(expr.syntax(), scope);
139 compute_expr_scopes(expr, scopes, scope);
140 }
141 }
142 }
143 }
144 if let Some(expr) = block.expr() {
145 scopes.set_scope(expr.syntax(), scope);
146 compute_expr_scopes(expr, scopes, scope);
147 }
148}
149
150fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) {
151 match expr {
152 ast::Expr::IfExpr(e) => {
153 let cond_scope = e
154 .condition()
155 .and_then(|cond| compute_cond_scopes(cond, scopes, scope));
156 if let Some(block) = e.then_branch() {
157 compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
158 }
159 if let Some(block) = e.else_branch() {
160 compute_block_scopes(block, scopes, scope);
161 }
162 }
163 ast::Expr::BlockExpr(e) => {
164 if let Some(block) = e.block() {
165 compute_block_scopes(block, scopes, scope);
166 }
167 }
168 ast::Expr::LoopExpr(e) => {
169 if let Some(block) = e.loop_body() {
170 compute_block_scopes(block, scopes, scope);
171 }
172 }
173 ast::Expr::WhileExpr(e) => {
174 let cond_scope = e
175 .condition()
176 .and_then(|cond| compute_cond_scopes(cond, scopes, scope));
177 if let Some(block) = e.loop_body() {
178 compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope));
179 }
180 }
181 ast::Expr::ForExpr(e) => {
182 if let Some(expr) = e.iterable() {
183 compute_expr_scopes(expr, scopes, scope);
184 }
185 let mut scope = scope;
186 if let Some(pat) = e.pat() {
187 scope = scopes.new_scope(scope);
188 scopes.add_bindings(scope, pat);
189 }
190 if let Some(block) = e.loop_body() {
191 compute_block_scopes(block, scopes, scope);
192 }
193 }
194 ast::Expr::LambdaExpr(e) => {
195 let scope = scopes.new_scope(scope);
196 scopes.add_params_bindings(scope, e.param_list());
197 if let Some(body) = e.body() {
198 scopes.set_scope(body.syntax(), scope);
199 compute_expr_scopes(body, scopes, scope);
200 }
201 }
202 ast::Expr::CallExpr(e) => {
203 compute_call_scopes(e.expr(), e.arg_list(), scopes, scope);
204 }
205 ast::Expr::MethodCallExpr(e) => {
206 compute_call_scopes(e.expr(), e.arg_list(), scopes, scope);
207 }
208 ast::Expr::MatchExpr(e) => {
209 if let Some(expr) = e.expr() {
210 compute_expr_scopes(expr, scopes, scope);
211 }
212 for arm in e.match_arm_list().into_iter().flat_map(|it| it.arms()) {
213 let scope = scopes.new_scope(scope);
214 for pat in arm.pats() {
215 scopes.add_bindings(scope, pat);
216 }
217 if let Some(expr) = arm.expr() {
218 compute_expr_scopes(expr, scopes, scope);
219 }
220 }
221 }
222 _ => expr
223 .syntax()
224 .children()
225 .filter_map(ast::Expr::cast)
226 .for_each(|expr| compute_expr_scopes(expr, scopes, scope)),
227 };
228
229 fn compute_call_scopes(
230 receiver: Option<ast::Expr>,
231 arg_list: Option<ast::ArgList>,
232 scopes: &mut FnScopes,
233 scope: ScopeId,
234 ) {
235 arg_list
236 .into_iter()
237 .flat_map(|it| it.args())
238 .chain(receiver)
239 .for_each(|expr| compute_expr_scopes(expr, scopes, scope));
240 }
241
242 fn compute_cond_scopes(
243 cond: ast::Condition,
244 scopes: &mut FnScopes,
245 scope: ScopeId,
246 ) -> Option<ScopeId> {
247 if let Some(expr) = cond.expr() {
248 compute_expr_scopes(expr, scopes, scope);
249 }
250 if let Some(pat) = cond.pat() {
251 let s = scopes.new_scope(scope);
252 scopes.add_bindings(s, pat);
253 Some(s)
254 } else {
255 None
256 }
257 }
258}
259
260pub fn resolve_local_name<'a>(
261 name_ref: ast::NameRef,
262 scopes: &'a FnScopes,
263) -> Option<&'a ScopeEntry> {
264 let mut shadowed = FxHashSet::default();
265 let ret = scopes
266 .scope_chain(name_ref.syntax())
267 .flat_map(|scope| scopes.entries(scope).iter())
268 .filter(|entry| shadowed.insert(entry.name()))
269 .filter(|entry| entry.name() == &name_ref.text())
270 .nth(0);
271 ret
272}
273
274#[cfg(test)]
275mod tests {
276 use ra_syntax::File;
277 use test_utils::extract_offset;
278 use ra_editor::{find_node_at_offset};
279
280 use super::*;
281
282
283 fn do_check(code: &str, expected: &[&str]) {
284 let (off, code) = extract_offset(code);
285 let code = {
286 let mut buf = String::new();
287 let off = u32::from(off) as usize;
288 buf.push_str(&code[..off]);
289 buf.push_str("marker");
290 buf.push_str(&code[off..]);
291 buf
292 };
293 let file = File::parse(&code);
294 let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
295 let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
296 let scopes = FnScopes::new(fn_def);
297 let actual = scopes
298 .scope_chain(marker.syntax())
299 .flat_map(|scope| scopes.entries(scope))
300 .map(|it| it.name())
301 .collect::<Vec<_>>();
302 assert_eq!(actual.as_slice(), expected);
303 }
304
305 #[test]
306 fn test_lambda_scope() {
307 do_check(
308 r"
309 fn quux(foo: i32) {
310 let f = |bar, baz: i32| {
311 <|>
312 };
313 }",
314 &["bar", "baz", "foo"],
315 );
316 }
317
318 #[test]
319 fn test_call_scope() {
320 do_check(
321 r"
322 fn quux() {
323 f(|x| <|> );
324 }",
325 &["x"],
326 );
327 }
328
329 #[test]
330 fn test_metod_call_scope() {
331 do_check(
332 r"
333 fn quux() {
334 z.f(|x| <|> );
335 }",
336 &["x"],
337 );
338 }
339
340 #[test]
341 fn test_loop_scope() {
342 do_check(
343 r"
344 fn quux() {
345 loop {
346 let x = ();
347 <|>
348 };
349 }",
350 &["x"],
351 );
352 }
353
354 #[test]
355 fn test_match() {
356 do_check(
357 r"
358 fn quux() {
359 match () {
360 Some(x) => {
361 <|>
362 }
363 };
364 }",
365 &["x"],
366 );
367 }
368
369 #[test]
370 fn test_shadow_variable() {
371 do_check(
372 r"
373 fn foo(x: String) {
374 let x : &str = &x<|>;
375 }",
376 &["x"],
377 );
378 }
379
380 fn do_check_local_name(code: &str, expected_offset: u32) {
381 let (off, code) = extract_offset(code);
382 let file = File::parse(&code);
383 let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
384 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
385
386 let scopes = FnScopes::new(fn_def);
387
388 let local_name_entry = resolve_local_name(name_ref, &scopes).unwrap();
389 let local_name = local_name_entry.ptr().resolve(&file);
390 let expected_name =
391 find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()).unwrap();
392 assert_eq!(local_name.range(), expected_name.syntax().range());
393 }
394
395 #[test]
396 fn test_resolve_local_name() {
397 do_check_local_name(
398 r#"
399 fn foo(x: i32, y: u32) {
400 {
401 let z = x * 2;
402 }
403 {
404 let t = x<|> * 3;
405 }
406 }"#,
407 21,
408 );
409 }
410
411 #[test]
412 fn test_resolve_local_name_declaration() {
413 do_check_local_name(
414 r#"
415 fn foo(x: String) {
416 let x : &str = &x<|>;
417 }"#,
418 21,
419 );
420 }
421
422 #[test]
423 fn test_resolve_local_name_shadow() {
424 do_check_local_name(
425 r"
426 fn foo(x: String) {
427 let x : &str = &x;
428 x<|>
429 }",
430 46,
431 );
432 }
433}