aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/descriptors/function/scope.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/descriptors/function/scope.rs')
-rw-r--r--crates/ra_analysis/src/descriptors/function/scope.rs435
1 files changed, 435 insertions, 0 deletions
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..5333a0a3b
--- /dev/null
+++ b/crates/ra_analysis/src/descriptors/function/scope.rs
@@ -0,0 +1,435 @@
1use rustc_hash::FxHashMap;
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 use rustc_hash::FxHashSet;
265
266 let mut shadowed = FxHashSet::default();
267 let ret = scopes
268 .scope_chain(name_ref.syntax())
269 .flat_map(|scope| scopes.entries(scope).iter())
270 .filter(|entry| shadowed.insert(entry.name()))
271 .filter(|entry| entry.name() == &name_ref.text())
272 .nth(0);
273 ret
274}
275
276#[cfg(test)]
277mod tests {
278 use ra_syntax::File;
279 use test_utils::extract_offset;
280 use ra_editor::{find_node_at_offset};
281
282 use super::*;
283
284
285 fn do_check(code: &str, expected: &[&str]) {
286 let (off, code) = extract_offset(code);
287 let code = {
288 let mut buf = String::new();
289 let off = u32::from(off) as usize;
290 buf.push_str(&code[..off]);
291 buf.push_str("marker");
292 buf.push_str(&code[off..]);
293 buf
294 };
295 let file = File::parse(&code);
296 let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
297 let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
298 let scopes = FnScopes::new(fn_def);
299 let actual = scopes
300 .scope_chain(marker.syntax())
301 .flat_map(|scope| scopes.entries(scope))
302 .map(|it| it.name())
303 .collect::<Vec<_>>();
304 assert_eq!(actual.as_slice(), expected);
305 }
306
307 #[test]
308 fn test_lambda_scope() {
309 do_check(
310 r"
311 fn quux(foo: i32) {
312 let f = |bar, baz: i32| {
313 <|>
314 };
315 }",
316 &["bar", "baz", "foo"],
317 );
318 }
319
320 #[test]
321 fn test_call_scope() {
322 do_check(
323 r"
324 fn quux() {
325 f(|x| <|> );
326 }",
327 &["x"],
328 );
329 }
330
331 #[test]
332 fn test_metod_call_scope() {
333 do_check(
334 r"
335 fn quux() {
336 z.f(|x| <|> );
337 }",
338 &["x"],
339 );
340 }
341
342 #[test]
343 fn test_loop_scope() {
344 do_check(
345 r"
346 fn quux() {
347 loop {
348 let x = ();
349 <|>
350 };
351 }",
352 &["x"],
353 );
354 }
355
356 #[test]
357 fn test_match() {
358 do_check(
359 r"
360 fn quux() {
361 match () {
362 Some(x) => {
363 <|>
364 }
365 };
366 }",
367 &["x"],
368 );
369 }
370
371 #[test]
372 fn test_shadow_variable() {
373 do_check(
374 r"
375 fn foo(x: String) {
376 let x : &str = &x<|>;
377 }",
378 &["x"],
379 );
380 }
381
382 fn do_check_local_name(code: &str, expected_offset: u32) {
383 let (off, code) = extract_offset(code);
384 let file = File::parse(&code);
385 let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
386 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
387
388 let scopes = FnScopes::new(fn_def);
389
390 let local_name_entry = resolve_local_name(name_ref, &scopes).unwrap();
391 let local_name = local_name_entry.ptr().resolve(&file);
392 let expected_name =
393 find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()).unwrap();
394 assert_eq!(local_name.range(), expected_name.syntax().range());
395 }
396
397 #[test]
398 fn test_resolve_local_name() {
399 do_check_local_name(
400 r#"
401 fn foo(x: i32, y: u32) {
402 {
403 let z = x * 2;
404 }
405 {
406 let t = x<|> * 3;
407 }
408 }"#,
409 21,
410 );
411 }
412
413 #[test]
414 fn test_resolve_local_name_declaration() {
415 do_check_local_name(
416 r#"
417 fn foo(x: String) {
418 let x : &str = &x<|>;
419 }"#,
420 21,
421 );
422 }
423
424 #[test]
425 fn test_resolve_local_name_shadow() {
426 do_check_local_name(
427 r"
428 fn foo(x: String) {
429 let x : &str = &x;
430 x<|>
431 }",
432 46,
433 );
434 }
435}