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