aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def/src/body
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_def/src/body')
-rw-r--r--crates/ra_hir_def/src/body/scope.rs219
1 files changed, 219 insertions, 0 deletions
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs
index 09a39e721..10cb87d37 100644
--- a/crates/ra_hir_def/src/body/scope.rs
+++ b/crates/ra_hir_def/src/body/scope.rs
@@ -67,6 +67,11 @@ impl ExprScopes {
67 std::iter::successors(scope, move |&scope| self.scopes[scope].parent) 67 std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
68 } 68 }
69 69
70 pub fn resolve_name_in_scope(&self, scope: ScopeId, name: &Name) -> Option<&ScopeEntry> {
71 self.scope_chain(Some(scope))
72 .find_map(|scope| self.entries(scope).iter().find(|it| it.name == *name))
73 }
74
70 pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> { 75 pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
71 self.scope_by_expr.get(&expr).copied() 76 self.scope_by_expr.get(&expr).copied()
72 } 77 }
@@ -163,3 +168,217 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
163 e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)), 168 e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
164 }; 169 };
165} 170}
171
172#[cfg(test)]
173mod tests {
174 use hir_expand::{name::AsName, Source};
175 use ra_db::{fixture::WithFixture, FileId, SourceDatabase};
176 use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
177 use test_utils::{assert_eq_text, extract_offset};
178
179 use crate::{db::DefDatabase2, test_db::TestDB, FunctionId, ModuleDefId};
180
181 fn find_function(db: &TestDB, file_id: FileId) -> FunctionId {
182 let krate = db.test_crate();
183 let crate_def_map = db.crate_def_map(krate);
184
185 let module = crate_def_map.modules_for_file(file_id).next().unwrap();
186 let (_, res) = crate_def_map[module].scope.entries().next().unwrap();
187 match res.def.take_values().unwrap() {
188 ModuleDefId::FunctionId(it) => it,
189 _ => panic!(),
190 }
191 }
192
193 fn do_check(code: &str, expected: &[&str]) {
194 let (off, code) = extract_offset(code);
195 let code = {
196 let mut buf = String::new();
197 let off = u32::from(off) as usize;
198 buf.push_str(&code[..off]);
199 buf.push_str("marker");
200 buf.push_str(&code[off..]);
201 buf
202 };
203
204 let (db, file_id) = TestDB::with_single_file(&code);
205
206 let file_syntax = db.parse(file_id).syntax_node();
207 let marker: ast::PathExpr = find_node_at_offset(&file_syntax, off).unwrap();
208 let function = find_function(&db, file_id);
209
210 let scopes = db.expr_scopes(function.into());
211 let (_body, source_map) = db.body_with_source_map(function.into());
212
213 let expr_id =
214 source_map.node_expr(Source { file_id: file_id.into(), ast: &marker.into() }).unwrap();
215 let scope = scopes.scope_for(expr_id);
216
217 let actual = scopes
218 .scope_chain(scope)
219 .flat_map(|scope| scopes.entries(scope))
220 .map(|it| it.name().to_string())
221 .collect::<Vec<_>>()
222 .join("\n");
223 let expected = expected.join("\n");
224 assert_eq_text!(&expected, &actual);
225 }
226
227 #[test]
228 fn test_lambda_scope() {
229 do_check(
230 r"
231 fn quux(foo: i32) {
232 let f = |bar, baz: i32| {
233 <|>
234 };
235 }",
236 &["bar", "baz", "foo"],
237 );
238 }
239
240 #[test]
241 fn test_call_scope() {
242 do_check(
243 r"
244 fn quux() {
245 f(|x| <|> );
246 }",
247 &["x"],
248 );
249 }
250
251 #[test]
252 fn test_method_call_scope() {
253 do_check(
254 r"
255 fn quux() {
256 z.f(|x| <|> );
257 }",
258 &["x"],
259 );
260 }
261
262 #[test]
263 fn test_loop_scope() {
264 do_check(
265 r"
266 fn quux() {
267 loop {
268 let x = ();
269 <|>
270 };
271 }",
272 &["x"],
273 );
274 }
275
276 #[test]
277 fn test_match() {
278 do_check(
279 r"
280 fn quux() {
281 match () {
282 Some(x) => {
283 <|>
284 }
285 };
286 }",
287 &["x"],
288 );
289 }
290
291 #[test]
292 fn test_shadow_variable() {
293 do_check(
294 r"
295 fn foo(x: String) {
296 let x : &str = &x<|>;
297 }",
298 &["x"],
299 );
300 }
301
302 fn do_check_local_name(code: &str, expected_offset: u32) {
303 let (off, code) = extract_offset(code);
304
305 let (db, file_id) = TestDB::with_single_file(&code);
306
307 let file = db.parse(file_id).ok().unwrap();
308 let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
309 .expect("failed to find a name at the target offset");
310 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
311
312 let function = find_function(&db, file_id);
313
314 let scopes = db.expr_scopes(function.into());
315 let (_body, source_map) = db.body_with_source_map(function.into());
316
317 let expr_scope = {
318 let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
319 let expr_id =
320 source_map.node_expr(Source { file_id: file_id.into(), ast: &expr_ast }).unwrap();
321 scopes.scope_for(expr_id).unwrap()
322 };
323
324 let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
325 let pat_src = source_map.pat_syntax(resolved.pat()).unwrap();
326
327 let local_name = pat_src.ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr());
328 assert_eq!(local_name.range(), expected_name.syntax().text_range());
329 }
330
331 #[test]
332 fn test_resolve_local_name() {
333 do_check_local_name(
334 r#"
335 fn foo(x: i32, y: u32) {
336 {
337 let z = x * 2;
338 }
339 {
340 let t = x<|> * 3;
341 }
342 }"#,
343 21,
344 );
345 }
346
347 #[test]
348 fn test_resolve_local_name_declaration() {
349 do_check_local_name(
350 r#"
351 fn foo(x: String) {
352 let x : &str = &x<|>;
353 }"#,
354 21,
355 );
356 }
357
358 #[test]
359 fn test_resolve_local_name_shadow() {
360 do_check_local_name(
361 r"
362 fn foo(x: String) {
363 let x : &str = &x;
364 x<|>
365 }
366 ",
367 53,
368 );
369 }
370
371 #[test]
372 fn ref_patterns_contribute_bindings() {
373 do_check_local_name(
374 r"
375 fn foo() {
376 if let Some(&from) = bar() {
377 from<|>;
378 }
379 }
380 ",
381 53,
382 );
383 }
384}