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