diff options
Diffstat (limited to 'crates/ra_hir/src/expr.rs')
-rw-r--r-- | crates/ra_hir/src/expr.rs | 204 |
1 files changed, 199 insertions, 5 deletions
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 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | pub(crate) mod scope; | ||
4 | pub(crate) mod validation; | 3 | pub(crate) mod validation; |
5 | 4 | ||
6 | use std::sync::Arc; | 5 | use std::sync::Arc; |
@@ -9,10 +8,11 @@ use ra_syntax::{ast, AstPtr}; | |||
9 | 8 | ||
10 | use crate::{db::HirDatabase, DefWithBody, HasSource, Resolver}; | 9 | use crate::{db::HirDatabase, DefWithBody, HasSource, Resolver}; |
11 | 10 | ||
12 | pub use self::scope::ExprScopes; | ||
13 | |||
14 | pub use hir_def::{ | 11 | pub use hir_def::{ |
15 | body::{Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource}, | 12 | body::{ |
13 | scope::{ExprScopes, ScopeEntry, ScopeId}, | ||
14 | Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource, | ||
15 | }, | ||
16 | expr::{ | 16 | expr::{ |
17 | ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, | 17 | ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, |
18 | MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, | 18 | MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, |
@@ -49,6 +49,11 @@ pub(crate) fn body_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<Body> { | |||
49 | db.body_with_source_map(def).0 | 49 | db.body_with_source_map(def).0 |
50 | } | 50 | } |
51 | 51 | ||
52 | pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> { | ||
53 | let body = db.body(def); | ||
54 | Arc::new(ExprScopes::new(&*body)) | ||
55 | } | ||
56 | |||
52 | // needs arbitrary_self_types to be a method... or maybe move to the def? | 57 | // needs arbitrary_self_types to be a method... or maybe move to the def? |
53 | pub(crate) fn resolver_for_expr( | 58 | pub(crate) fn resolver_for_expr( |
54 | db: &impl HirDatabase, | 59 | db: &impl HirDatabase, |
@@ -62,7 +67,7 @@ pub(crate) fn resolver_for_expr( | |||
62 | pub(crate) fn resolver_for_scope( | 67 | pub(crate) fn resolver_for_scope( |
63 | db: &impl HirDatabase, | 68 | db: &impl HirDatabase, |
64 | owner: DefWithBody, | 69 | owner: DefWithBody, |
65 | scope_id: Option<scope::ScopeId>, | 70 | scope_id: Option<ScopeId>, |
66 | ) -> Resolver { | 71 | ) -> Resolver { |
67 | let mut r = owner.resolver(db); | 72 | let mut r = owner.resolver(db); |
68 | let scopes = db.expr_scopes(owner); | 73 | let scopes = db.expr_scopes(owner); |
@@ -72,3 +77,192 @@ pub(crate) fn resolver_for_scope( | |||
72 | } | 77 | } |
73 | r | 78 | r |
74 | } | 79 | } |
80 | |||
81 | #[cfg(test)] | ||
82 | mod tests { | ||
83 | use hir_expand::Source; | ||
84 | use ra_db::{fixture::WithFixture, SourceDatabase}; | ||
85 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; | ||
86 | use test_utils::{assert_eq_text, extract_offset}; | ||
87 | |||
88 | use crate::{source_binder::SourceAnalyzer, test_db::TestDB}; | ||
89 | |||
90 | fn do_check(code: &str, expected: &[&str]) { | ||
91 | let (off, code) = extract_offset(code); | ||
92 | let code = { | ||
93 | let mut buf = String::new(); | ||
94 | let off = u32::from(off) as usize; | ||
95 | buf.push_str(&code[..off]); | ||
96 | buf.push_str("marker"); | ||
97 | buf.push_str(&code[off..]); | ||
98 | buf | ||
99 | }; | ||
100 | |||
101 | let (db, file_id) = TestDB::with_single_file(&code); | ||
102 | |||
103 | let file = db.parse(file_id).ok().unwrap(); | ||
104 | let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap(); | ||
105 | let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None); | ||
106 | |||
107 | let scopes = analyzer.scopes(); | ||
108 | let expr_id = analyzer | ||
109 | .body_source_map() | ||
110 | .node_expr(Source { file_id: file_id.into(), ast: &marker.into() }) | ||
111 | .unwrap(); | ||
112 | let scope = scopes.scope_for(expr_id); | ||
113 | |||
114 | let actual = scopes | ||
115 | .scope_chain(scope) | ||
116 | .flat_map(|scope| scopes.entries(scope)) | ||
117 | .map(|it| it.name().to_string()) | ||
118 | .collect::<Vec<_>>() | ||
119 | .join("\n"); | ||
120 | let expected = expected.join("\n"); | ||
121 | assert_eq_text!(&expected, &actual); | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn test_lambda_scope() { | ||
126 | do_check( | ||
127 | r" | ||
128 | fn quux(foo: i32) { | ||
129 | let f = |bar, baz: i32| { | ||
130 | <|> | ||
131 | }; | ||
132 | }", | ||
133 | &["bar", "baz", "foo"], | ||
134 | ); | ||
135 | } | ||
136 | |||
137 | #[test] | ||
138 | fn test_call_scope() { | ||
139 | do_check( | ||
140 | r" | ||
141 | fn quux() { | ||
142 | f(|x| <|> ); | ||
143 | }", | ||
144 | &["x"], | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn test_method_call_scope() { | ||
150 | do_check( | ||
151 | r" | ||
152 | fn quux() { | ||
153 | z.f(|x| <|> ); | ||
154 | }", | ||
155 | &["x"], | ||
156 | ); | ||
157 | } | ||
158 | |||
159 | #[test] | ||
160 | fn test_loop_scope() { | ||
161 | do_check( | ||
162 | r" | ||
163 | fn quux() { | ||
164 | loop { | ||
165 | let x = (); | ||
166 | <|> | ||
167 | }; | ||
168 | }", | ||
169 | &["x"], | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn test_match() { | ||
175 | do_check( | ||
176 | r" | ||
177 | fn quux() { | ||
178 | match () { | ||
179 | Some(x) => { | ||
180 | <|> | ||
181 | } | ||
182 | }; | ||
183 | }", | ||
184 | &["x"], | ||
185 | ); | ||
186 | } | ||
187 | |||
188 | #[test] | ||
189 | fn test_shadow_variable() { | ||
190 | do_check( | ||
191 | r" | ||
192 | fn foo(x: String) { | ||
193 | let x : &str = &x<|>; | ||
194 | }", | ||
195 | &["x"], | ||
196 | ); | ||
197 | } | ||
198 | |||
199 | fn do_check_local_name(code: &str, expected_offset: u32) { | ||
200 | let (off, code) = extract_offset(code); | ||
201 | |||
202 | let (db, file_id) = TestDB::with_single_file(&code); | ||
203 | let file = db.parse(file_id).ok().unwrap(); | ||
204 | let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()) | ||
205 | .expect("failed to find a name at the target offset"); | ||
206 | let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap(); | ||
207 | let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None); | ||
208 | |||
209 | let local_name_entry = analyzer.resolve_local_name(&name_ref).unwrap(); | ||
210 | let local_name = | ||
211 | local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); | ||
212 | assert_eq!(local_name.range(), expected_name.syntax().text_range()); | ||
213 | } | ||
214 | |||
215 | #[test] | ||
216 | fn test_resolve_local_name() { | ||
217 | do_check_local_name( | ||
218 | r#" | ||
219 | fn foo(x: i32, y: u32) { | ||
220 | { | ||
221 | let z = x * 2; | ||
222 | } | ||
223 | { | ||
224 | let t = x<|> * 3; | ||
225 | } | ||
226 | }"#, | ||
227 | 21, | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_resolve_local_name_declaration() { | ||
233 | do_check_local_name( | ||
234 | r#" | ||
235 | fn foo(x: String) { | ||
236 | let x : &str = &x<|>; | ||
237 | }"#, | ||
238 | 21, | ||
239 | ); | ||
240 | } | ||
241 | |||
242 | #[test] | ||
243 | fn test_resolve_local_name_shadow() { | ||
244 | do_check_local_name( | ||
245 | r" | ||
246 | fn foo(x: String) { | ||
247 | let x : &str = &x; | ||
248 | x<|> | ||
249 | } | ||
250 | ", | ||
251 | 53, | ||
252 | ); | ||
253 | } | ||
254 | |||
255 | #[test] | ||
256 | fn ref_patterns_contribute_bindings() { | ||
257 | do_check_local_name( | ||
258 | r" | ||
259 | fn foo() { | ||
260 | if let Some(&from) = bar() { | ||
261 | from<|>; | ||
262 | } | ||
263 | } | ||
264 | ", | ||
265 | 53, | ||
266 | ); | ||
267 | } | ||
268 | } | ||