aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/expr/scope.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir/src/expr/scope.rs')
-rw-r--r--crates/ra_hir/src/expr/scope.rs354
1 files changed, 0 insertions, 354 deletions
diff --git a/crates/ra_hir/src/expr/scope.rs b/crates/ra_hir/src/expr/scope.rs
deleted file mode 100644
index 0e49a28d6..000000000
--- a/crates/ra_hir/src/expr/scope.rs
+++ /dev/null
@@ -1,354 +0,0 @@
1//! FIXME: write short doc here
2
3use std::sync::Arc;
4
5use ra_arena::{impl_arena_id, Arena, RawId};
6use rustc_hash::FxHashMap;
7
8use crate::{
9 db::HirDatabase,
10 expr::{Body, Expr, ExprId, Pat, PatId, Statement},
11 DefWithBody, Name,
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct ScopeId(RawId);
16impl_arena_id!(ScopeId);
17
18#[derive(Debug, PartialEq, Eq)]
19pub struct ExprScopes {
20 pub(crate) body: Arc<Body>,
21 scopes: Arena<ScopeId, ScopeData>,
22 scope_by_expr: FxHashMap<ExprId, ScopeId>,
23}
24
25#[derive(Debug, PartialEq, Eq)]
26pub(crate) struct ScopeEntry {
27 name: Name,
28 pat: PatId,
29}
30
31impl ScopeEntry {
32 pub(crate) fn name(&self) -> &Name {
33 &self.name
34 }
35
36 pub(crate) fn pat(&self) -> PatId {
37 self.pat
38 }
39}
40
41#[derive(Debug, PartialEq, Eq)]
42pub(crate) struct ScopeData {
43 parent: Option<ScopeId>,
44 entries: Vec<ScopeEntry>,
45}
46
47impl ExprScopes {
48 pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> {
49 let body = db.body(def);
50 let res = ExprScopes::new(body);
51 Arc::new(res)
52 }
53
54 fn new(body: Arc<Body>) -> ExprScopes {
55 let mut scopes = ExprScopes {
56 body: body.clone(),
57 scopes: Arena::default(),
58 scope_by_expr: FxHashMap::default(),
59 };
60 let root = scopes.root_scope();
61 scopes.add_params_bindings(root, body.params());
62 compute_expr_scopes(body.body_expr(), &body, &mut scopes, root);
63 scopes
64 }
65
66 pub(crate) fn entries(&self, scope: ScopeId) -> &[ScopeEntry] {
67 &self.scopes[scope].entries
68 }
69
70 pub(crate) fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ {
71 std::iter::successors(scope, move |&scope| self.scopes[scope].parent)
72 }
73
74 pub(crate) fn scope_for(&self, expr: ExprId) -> Option<ScopeId> {
75 self.scope_by_expr.get(&expr).copied()
76 }
77
78 pub(crate) fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> {
79 &self.scope_by_expr
80 }
81
82 fn root_scope(&mut self) -> ScopeId {
83 self.scopes.alloc(ScopeData { parent: None, entries: vec![] })
84 }
85
86 fn new_scope(&mut self, parent: ScopeId) -> ScopeId {
87 self.scopes.alloc(ScopeData { parent: Some(parent), entries: vec![] })
88 }
89
90 fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
91 match &body[pat] {
92 Pat::Bind { name, .. } => {
93 // bind can have a sub pattern, but it's actually not allowed
94 // to bind to things in there
95 let entry = ScopeEntry { name: name.clone(), pat };
96 self.scopes[scope].entries.push(entry)
97 }
98 p => p.walk_child_pats(|pat| self.add_bindings(body, scope, pat)),
99 }
100 }
101
102 fn add_params_bindings(&mut self, scope: ScopeId, params: &[PatId]) {
103 let body = Arc::clone(&self.body);
104 params.iter().for_each(|pat| self.add_bindings(&body, scope, *pat));
105 }
106
107 fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
108 self.scope_by_expr.insert(node, scope);
109 }
110}
111
112fn compute_block_scopes(
113 statements: &[Statement],
114 tail: Option<ExprId>,
115 body: &Body,
116 scopes: &mut ExprScopes,
117 mut scope: ScopeId,
118) {
119 for stmt in statements {
120 match stmt {
121 Statement::Let { pat, initializer, .. } => {
122 if let Some(expr) = initializer {
123 scopes.set_scope(*expr, scope);
124 compute_expr_scopes(*expr, body, scopes, scope);
125 }
126 scope = scopes.new_scope(scope);
127 scopes.add_bindings(body, scope, *pat);
128 }
129 Statement::Expr(expr) => {
130 scopes.set_scope(*expr, scope);
131 compute_expr_scopes(*expr, body, scopes, scope);
132 }
133 }
134 }
135 if let Some(expr) = tail {
136 compute_expr_scopes(expr, body, scopes, scope);
137 }
138}
139
140fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) {
141 scopes.set_scope(expr, scope);
142 match &body[expr] {
143 Expr::Block { statements, tail } => {
144 compute_block_scopes(&statements, *tail, body, scopes, scope);
145 }
146 Expr::For { iterable, pat, body: body_expr } => {
147 compute_expr_scopes(*iterable, body, scopes, scope);
148 let scope = scopes.new_scope(scope);
149 scopes.add_bindings(body, scope, *pat);
150 compute_expr_scopes(*body_expr, body, scopes, scope);
151 }
152 Expr::Lambda { args, body: body_expr, .. } => {
153 let scope = scopes.new_scope(scope);
154 scopes.add_params_bindings(scope, &args);
155 compute_expr_scopes(*body_expr, body, scopes, scope);
156 }
157 Expr::Match { expr, arms } => {
158 compute_expr_scopes(*expr, body, scopes, scope);
159 for arm in arms {
160 let scope = scopes.new_scope(scope);
161 for pat in &arm.pats {
162 scopes.add_bindings(body, scope, *pat);
163 }
164 scopes.set_scope(arm.expr, scope);
165 compute_expr_scopes(arm.expr, body, scopes, scope);
166 }
167 }
168 e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
169 };
170}
171
172#[cfg(test)]
173mod tests {
174 use ra_db::{fixture::WithFixture, SourceDatabase};
175 use ra_syntax::{algo::find_node_at_offset, ast, AstNode};
176 use test_utils::{assert_eq_text, extract_offset};
177
178 use crate::{source_binder::SourceAnalyzer, test_db::TestDB};
179
180 fn do_check(code: &str, expected: &[&str]) {
181 let (off, code) = extract_offset(code);
182 let code = {
183 let mut buf = String::new();
184 let off = u32::from(off) as usize;
185 buf.push_str(&code[..off]);
186 buf.push_str("marker");
187 buf.push_str(&code[off..]);
188 buf
189 };
190
191 let (db, file_id) = TestDB::with_single_file(&code);
192 let file = db.parse(file_id).ok().unwrap();
193 let marker: ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
194 let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None);
195
196 let scopes = analyzer.scopes();
197 let expr_id = analyzer.body_source_map().node_expr(&marker.into()).unwrap();
198 let scope = scopes.scope_for(expr_id);
199
200 let actual = scopes
201 .scope_chain(scope)
202 .flat_map(|scope| scopes.entries(scope))
203 .map(|it| it.name().to_string())
204 .collect::<Vec<_>>()
205 .join("\n");
206 let expected = expected.join("\n");
207 assert_eq_text!(&expected, &actual);
208 }
209
210 #[test]
211 fn test_lambda_scope() {
212 do_check(
213 r"
214 fn quux(foo: i32) {
215 let f = |bar, baz: i32| {
216 <|>
217 };
218 }",
219 &["bar", "baz", "foo"],
220 );
221 }
222
223 #[test]
224 fn test_call_scope() {
225 do_check(
226 r"
227 fn quux() {
228 f(|x| <|> );
229 }",
230 &["x"],
231 );
232 }
233
234 #[test]
235 fn test_method_call_scope() {
236 do_check(
237 r"
238 fn quux() {
239 z.f(|x| <|> );
240 }",
241 &["x"],
242 );
243 }
244
245 #[test]
246 fn test_loop_scope() {
247 do_check(
248 r"
249 fn quux() {
250 loop {
251 let x = ();
252 <|>
253 };
254 }",
255 &["x"],
256 );
257 }
258
259 #[test]
260 fn test_match() {
261 do_check(
262 r"
263 fn quux() {
264 match () {
265 Some(x) => {
266 <|>
267 }
268 };
269 }",
270 &["x"],
271 );
272 }
273
274 #[test]
275 fn test_shadow_variable() {
276 do_check(
277 r"
278 fn foo(x: String) {
279 let x : &str = &x<|>;
280 }",
281 &["x"],
282 );
283 }
284
285 fn do_check_local_name(code: &str, expected_offset: u32) {
286 let (off, code) = extract_offset(code);
287
288 let (db, file_id) = TestDB::with_single_file(&code);
289 let file = db.parse(file_id).ok().unwrap();
290 let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into())
291 .expect("failed to find a name at the target offset");
292 let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
293 let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None);
294
295 let local_name_entry = analyzer.resolve_local_name(&name_ref).unwrap();
296 let local_name =
297 local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr());
298 assert_eq!(local_name.range(), expected_name.syntax().text_range());
299 }
300
301 #[test]
302 fn test_resolve_local_name() {
303 do_check_local_name(
304 r#"
305 fn foo(x: i32, y: u32) {
306 {
307 let z = x * 2;
308 }
309 {
310 let t = x<|> * 3;
311 }
312 }"#,
313 21,
314 );
315 }
316
317 #[test]
318 fn test_resolve_local_name_declaration() {
319 do_check_local_name(
320 r#"
321 fn foo(x: String) {
322 let x : &str = &x<|>;
323 }"#,
324 21,
325 );
326 }
327
328 #[test]
329 fn test_resolve_local_name_shadow() {
330 do_check_local_name(
331 r"
332 fn foo(x: String) {
333 let x : &str = &x;
334 x<|>
335 }
336 ",
337 53,
338 );
339 }
340
341 #[test]
342 fn ref_patterns_contribute_bindings() {
343 do_check_local_name(
344 r"
345 fn foo() {
346 if let Some(&from) = bar() {
347 from<|>;
348 }
349 }
350 ",
351 53,
352 );
353 }
354}