From f924ae3b86dc5e978071b6f8308b9f357415780b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Thu, 14 Nov 2019 11:56:13 +0300 Subject: Move scopes to hir_def --- crates/ra_hir/src/expr.rs | 204 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 5 deletions(-) (limited to 'crates/ra_hir/src/expr.rs') diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index d19f5d14c..f02104b2d 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -1,6 +1,5 @@ //! FIXME: write short doc here -pub(crate) mod scope; pub(crate) mod validation; use std::sync::Arc; @@ -9,10 +8,11 @@ use ra_syntax::{ast, AstPtr}; use crate::{db::HirDatabase, DefWithBody, HasSource, Resolver}; -pub use self::scope::ExprScopes; - pub use hir_def::{ - body::{Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource}, + body::{ + scope::{ExprScopes, ScopeEntry, ScopeId}, + Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource, + }, expr::{ ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, @@ -49,6 +49,11 @@ pub(crate) fn body_query(db: &impl HirDatabase, def: DefWithBody) -> Arc { db.body_with_source_map(def).0 } +pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc { + let body = db.body(def); + Arc::new(ExprScopes::new(&*body)) +} + // needs arbitrary_self_types to be a method... or maybe move to the def? pub(crate) fn resolver_for_expr( db: &impl HirDatabase, @@ -62,7 +67,7 @@ pub(crate) fn resolver_for_expr( pub(crate) fn resolver_for_scope( db: &impl HirDatabase, owner: DefWithBody, - scope_id: Option, + scope_id: Option, ) -> Resolver { let mut r = owner.resolver(db); let scopes = db.expr_scopes(owner); @@ -72,3 +77,192 @@ pub(crate) fn resolver_for_scope( } r } + +#[cfg(test)] +mod tests { + use hir_expand::Source; + use ra_db::{fixture::WithFixture, SourceDatabase}; + use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; + use test_utils::{assert_eq_text, extract_offset}; + + use crate::{source_binder::SourceAnalyzer, test_db::TestDB}; + + fn do_check(code: &str, expected: &[&str]) { + let (off, code) = extract_offset(code); + let code = { + let mut buf = String::new(); + let off = u32::from(off) as usize; + buf.push_str(&code[..off]); + buf.push_str("marker"); + buf.push_str(&code[off..]); + buf + }; + + let (db, file_id) = TestDB::with_single_file(&code); + + let file = db.parse(file_id).ok().unwrap(); + let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap(); + let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None); + + let scopes = analyzer.scopes(); + let expr_id = analyzer + .body_source_map() + .node_expr(Source { file_id: file_id.into(), ast: &marker.into() }) + .unwrap(); + let scope = scopes.scope_for(expr_id); + + let actual = scopes + .scope_chain(scope) + .flat_map(|scope| scopes.entries(scope)) + .map(|it| it.name().to_string()) + .collect::>() + .join("\n"); + let expected = expected.join("\n"); + assert_eq_text!(&expected, &actual); + } + + #[test] + fn test_lambda_scope() { + do_check( + r" + fn quux(foo: i32) { + let f = |bar, baz: i32| { + <|> + }; + }", + &["bar", "baz", "foo"], + ); + } + + #[test] + fn test_call_scope() { + do_check( + r" + fn quux() { + f(|x| <|> ); + }", + &["x"], + ); + } + + #[test] + fn test_method_call_scope() { + do_check( + r" + fn quux() { + z.f(|x| <|> ); + }", + &["x"], + ); + } + + #[test] + fn test_loop_scope() { + do_check( + r" + fn quux() { + loop { + let x = (); + <|> + }; + }", + &["x"], + ); + } + + #[test] + fn test_match() { + do_check( + r" + fn quux() { + match () { + Some(x) => { + <|> + } + }; + }", + &["x"], + ); + } + + #[test] + fn test_shadow_variable() { + do_check( + r" + fn foo(x: String) { + let x : &str = &x<|>; + }", + &["x"], + ); + } + + fn do_check_local_name(code: &str, expected_offset: u32) { + let (off, code) = extract_offset(code); + + let (db, file_id) = TestDB::with_single_file(&code); + let file = db.parse(file_id).ok().unwrap(); + let expected_name = find_node_at_offset::(file.syntax(), expected_offset.into()) + .expect("failed to find a name at the target offset"); + let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap(); + let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None); + + let local_name_entry = analyzer.resolve_local_name(&name_ref).unwrap(); + let local_name = + local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + assert_eq!(local_name.range(), expected_name.syntax().text_range()); + } + + #[test] + fn test_resolve_local_name() { + do_check_local_name( + r#" + fn foo(x: i32, y: u32) { + { + let z = x * 2; + } + { + let t = x<|> * 3; + } + }"#, + 21, + ); + } + + #[test] + fn test_resolve_local_name_declaration() { + do_check_local_name( + r#" + fn foo(x: String) { + let x : &str = &x<|>; + }"#, + 21, + ); + } + + #[test] + fn test_resolve_local_name_shadow() { + do_check_local_name( + r" + fn foo(x: String) { + let x : &str = &x; + x<|> + } + ", + 53, + ); + } + + #[test] + fn ref_patterns_contribute_bindings() { + do_check_local_name( + r" + fn foo() { + if let Some(&from) = bar() { + from<|>; + } + } + ", + 53, + ); + } +} -- cgit v1.2.3