diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-13 12:17:48 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-13 12:17:48 +0100 |
commit | 8887782c4ab97d22f3d5c10e142407e4371c5c61 (patch) | |
tree | 80b60b2c0c2f6104b98e16648b95d99d9b1d3463 /crates/ra_hir/src/expr | |
parent | 34a05b7fea4add78446b2d93a64538982abacb9f (diff) | |
parent | 2facb5e061971afbf6bd2fabe3966d5de9d46489 (diff) |
Merge #1129
1129: introduce SourceAnalyzer API for ides r=matklad a=matklad
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_hir/src/expr')
-rw-r--r-- | crates/ra_hir/src/expr/scope.rs | 232 |
1 files changed, 49 insertions, 183 deletions
diff --git a/crates/ra_hir/src/expr/scope.rs b/crates/ra_hir/src/expr/scope.rs index 725b6c00e..58f365128 100644 --- a/crates/ra_hir/src/expr/scope.rs +++ b/crates/ra_hir/src/expr/scope.rs | |||
@@ -1,17 +1,11 @@ | |||
1 | use std::sync::Arc; | 1 | use std::sync::Arc; |
2 | 2 | ||
3 | use rustc_hash::{FxHashMap, FxHashSet}; | 3 | use rustc_hash::FxHashMap; |
4 | |||
5 | use ra_syntax::{ | ||
6 | AstNode, SyntaxNode, TextUnit, TextRange, SyntaxNodePtr, AstPtr, | ||
7 | algo::generate, | ||
8 | ast, | ||
9 | }; | ||
10 | use ra_arena::{Arena, RawId, impl_arena_id}; | 4 | use ra_arena::{Arena, RawId, impl_arena_id}; |
11 | 5 | ||
12 | use crate::{ | 6 | use crate::{ |
13 | Name, AsName,DefWithBody, Either, | 7 | Name, DefWithBody, |
14 | expr::{PatId, ExprId, Pat, Expr, Body, Statement, BodySourceMap}, | 8 | expr::{PatId, ExprId, Pat, Expr, Body, Statement}, |
15 | HirDatabase, | 9 | HirDatabase, |
16 | }; | 10 | }; |
17 | 11 | ||
@@ -23,23 +17,32 @@ impl_arena_id!(ScopeId); | |||
23 | pub struct ExprScopes { | 17 | pub struct ExprScopes { |
24 | body: Arc<Body>, | 18 | body: Arc<Body>, |
25 | scopes: Arena<ScopeId, ScopeData>, | 19 | scopes: Arena<ScopeId, ScopeData>, |
26 | scope_for: FxHashMap<ExprId, ScopeId>, | 20 | scope_by_expr: FxHashMap<ExprId, ScopeId>, |
27 | } | 21 | } |
28 | 22 | ||
29 | #[derive(Debug, PartialEq, Eq)] | 23 | #[derive(Debug, PartialEq, Eq)] |
30 | pub struct ScopeEntry { | 24 | pub(crate) struct ScopeEntry { |
31 | name: Name, | 25 | name: Name, |
32 | pat: PatId, | 26 | pat: PatId, |
33 | } | 27 | } |
34 | 28 | ||
29 | impl ScopeEntry { | ||
30 | pub(crate) fn name(&self) -> &Name { | ||
31 | &self.name | ||
32 | } | ||
33 | |||
34 | pub(crate) fn pat(&self) -> PatId { | ||
35 | self.pat | ||
36 | } | ||
37 | } | ||
38 | |||
35 | #[derive(Debug, PartialEq, Eq)] | 39 | #[derive(Debug, PartialEq, Eq)] |
36 | pub struct ScopeData { | 40 | pub(crate) struct ScopeData { |
37 | parent: Option<ScopeId>, | 41 | parent: Option<ScopeId>, |
38 | entries: Vec<ScopeEntry>, | 42 | entries: Vec<ScopeEntry>, |
39 | } | 43 | } |
40 | 44 | ||
41 | impl ExprScopes { | 45 | impl ExprScopes { |
42 | // FIXME: This should take something more general than Function | ||
43 | pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> { | 46 | pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> { |
44 | let body = db.body_hir(def); | 47 | let body = db.body_hir(def); |
45 | let res = ExprScopes::new(body); | 48 | let res = ExprScopes::new(body); |
@@ -50,7 +53,7 @@ impl ExprScopes { | |||
50 | let mut scopes = ExprScopes { | 53 | let mut scopes = ExprScopes { |
51 | body: body.clone(), | 54 | body: body.clone(), |
52 | scopes: Arena::default(), | 55 | scopes: Arena::default(), |
53 | scope_for: FxHashMap::default(), | 56 | scope_by_expr: FxHashMap::default(), |
54 | }; | 57 | }; |
55 | let root = scopes.root_scope(); | 58 | let root = scopes.root_scope(); |
56 | scopes.add_params_bindings(root, body.params()); | 59 | scopes.add_params_bindings(root, body.params()); |
@@ -58,19 +61,23 @@ impl ExprScopes { | |||
58 | scopes | 61 | scopes |
59 | } | 62 | } |
60 | 63 | ||
61 | pub fn body(&self) -> Arc<Body> { | 64 | pub(crate) fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { |
62 | self.body.clone() | ||
63 | } | ||
64 | |||
65 | pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { | ||
66 | &self.scopes[scope].entries | 65 | &self.scopes[scope].entries |
67 | } | 66 | } |
68 | 67 | ||
69 | pub fn scope_chain_for<'a>( | 68 | pub(crate) fn scope_chain<'a>( |
70 | &'a self, | 69 | &'a self, |
71 | scope: Option<ScopeId>, | 70 | scope: Option<ScopeId>, |
72 | ) -> impl Iterator<Item = ScopeId> + 'a { | 71 | ) -> impl Iterator<Item = ScopeId> + 'a { |
73 | generate(scope, move |&scope| self.scopes[scope].parent) | 72 | std::iter::successors(scope, move |&scope| self.scopes[scope].parent) |
73 | } | ||
74 | |||
75 | pub(crate) fn scope_for(&self, expr: ExprId) -> Option<ScopeId> { | ||
76 | self.scope_by_expr.get(&expr).map(|&scope| scope) | ||
77 | } | ||
78 | |||
79 | pub(crate) fn scope_by_expr(&self) -> &FxHashMap<ExprId, ScopeId> { | ||
80 | &self.scope_by_expr | ||
74 | } | 81 | } |
75 | 82 | ||
76 | fn root_scope(&mut self) -> ScopeId { | 83 | fn root_scope(&mut self) -> ScopeId { |
@@ -99,130 +106,7 @@ impl ExprScopes { | |||
99 | } | 106 | } |
100 | 107 | ||
101 | fn set_scope(&mut self, node: ExprId, scope: ScopeId) { | 108 | fn set_scope(&mut self, node: ExprId, scope: ScopeId) { |
102 | self.scope_for.insert(node, scope); | 109 | self.scope_by_expr.insert(node, scope); |
103 | } | ||
104 | |||
105 | pub fn scope_for(&self, expr: ExprId) -> Option<ScopeId> { | ||
106 | self.scope_for.get(&expr).map(|&scope| scope) | ||
107 | } | ||
108 | } | ||
109 | |||
110 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
111 | pub struct ScopesWithSourceMap { | ||
112 | pub source_map: Arc<BodySourceMap>, | ||
113 | pub scopes: Arc<ExprScopes>, | ||
114 | } | ||
115 | |||
116 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
117 | pub struct ScopeEntryWithSyntax { | ||
118 | name: Name, | ||
119 | ptr: Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>, | ||
120 | } | ||
121 | |||
122 | impl ScopeEntryWithSyntax { | ||
123 | pub fn name(&self) -> &Name { | ||
124 | &self.name | ||
125 | } | ||
126 | |||
127 | pub fn ptr(&self) -> Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>> { | ||
128 | self.ptr | ||
129 | } | ||
130 | } | ||
131 | |||
132 | impl ScopesWithSourceMap { | ||
133 | fn scope_chain<'a>(&'a self, node: &SyntaxNode) -> impl Iterator<Item = ScopeId> + 'a { | ||
134 | generate(self.scope_for(node), move |&scope| self.scopes.scopes[scope].parent) | ||
135 | } | ||
136 | |||
137 | pub fn scope_for_offset(&self, offset: TextUnit) -> Option<ScopeId> { | ||
138 | self.scopes | ||
139 | .scope_for | ||
140 | .iter() | ||
141 | .filter_map(|(id, scope)| Some((self.source_map.expr_syntax(*id)?, scope))) | ||
142 | // find containing scope | ||
143 | .min_by_key(|(ptr, _scope)| { | ||
144 | (!(ptr.range().start() <= offset && offset <= ptr.range().end()), ptr.range().len()) | ||
145 | }) | ||
146 | .map(|(ptr, scope)| self.adjust(ptr, *scope, offset)) | ||
147 | } | ||
148 | |||
149 | // XXX: during completion, cursor might be outside of any particular | ||
150 | // expression. Try to figure out the correct scope... | ||
151 | // FIXME: move this to source binder? | ||
152 | fn adjust(&self, ptr: SyntaxNodePtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId { | ||
153 | let r = ptr.range(); | ||
154 | let child_scopes = self | ||
155 | .scopes | ||
156 | .scope_for | ||
157 | .iter() | ||
158 | .filter_map(|(id, scope)| Some((self.source_map.expr_syntax(*id)?, scope))) | ||
159 | .map(|(ptr, scope)| (ptr.range(), scope)) | ||
160 | .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r); | ||
161 | |||
162 | child_scopes | ||
163 | .max_by(|(r1, _), (r2, _)| { | ||
164 | if r2.is_subrange(&r1) { | ||
165 | std::cmp::Ordering::Greater | ||
166 | } else if r1.is_subrange(&r2) { | ||
167 | std::cmp::Ordering::Less | ||
168 | } else { | ||
169 | r1.start().cmp(&r2.start()) | ||
170 | } | ||
171 | }) | ||
172 | .map(|(_ptr, scope)| *scope) | ||
173 | .unwrap_or(original_scope) | ||
174 | } | ||
175 | |||
176 | pub fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option<ScopeEntryWithSyntax> { | ||
177 | let mut shadowed = FxHashSet::default(); | ||
178 | let name = name_ref.as_name(); | ||
179 | let ret = self | ||
180 | .scope_chain(name_ref.syntax()) | ||
181 | .flat_map(|scope| self.scopes.entries(scope).iter()) | ||
182 | .filter(|entry| shadowed.insert(entry.name())) | ||
183 | .filter(|entry| entry.name() == &name) | ||
184 | .nth(0); | ||
185 | ret.and_then(|entry| { | ||
186 | Some(ScopeEntryWithSyntax { | ||
187 | name: entry.name().clone(), | ||
188 | ptr: self.source_map.pat_syntax(entry.pat())?, | ||
189 | }) | ||
190 | }) | ||
191 | } | ||
192 | |||
193 | pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec<ReferenceDescriptor> { | ||
194 | let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); | ||
195 | let ptr = Either::A(AstPtr::new(pat.into())); | ||
196 | fn_def | ||
197 | .syntax() | ||
198 | .descendants() | ||
199 | .filter_map(ast::NameRef::cast) | ||
200 | .filter(|name_ref| match self.resolve_local_name(*name_ref) { | ||
201 | None => false, | ||
202 | Some(entry) => entry.ptr() == ptr, | ||
203 | }) | ||
204 | .map(|name_ref| ReferenceDescriptor { | ||
205 | name: name_ref.syntax().text().to_string(), | ||
206 | range: name_ref.syntax().range(), | ||
207 | }) | ||
208 | .collect() | ||
209 | } | ||
210 | |||
211 | pub fn scope_for(&self, node: &SyntaxNode) -> Option<ScopeId> { | ||
212 | node.ancestors() | ||
213 | .map(SyntaxNodePtr::new) | ||
214 | .filter_map(|ptr| self.source_map.syntax_expr(ptr)) | ||
215 | .find_map(|it| self.scopes.scope_for(it)) | ||
216 | } | ||
217 | } | ||
218 | |||
219 | impl ScopeEntry { | ||
220 | pub fn name(&self) -> &Name { | ||
221 | &self.name | ||
222 | } | ||
223 | |||
224 | pub fn pat(&self) -> PatId { | ||
225 | self.pat | ||
226 | } | 110 | } |
227 | } | 111 | } |
228 | 112 | ||
@@ -286,22 +170,13 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope | |||
286 | }; | 170 | }; |
287 | } | 171 | } |
288 | 172 | ||
289 | #[derive(Debug)] | ||
290 | pub struct ReferenceDescriptor { | ||
291 | pub range: TextRange, | ||
292 | pub name: String, | ||
293 | } | ||
294 | |||
295 | #[cfg(test)] | 173 | #[cfg(test)] |
296 | mod tests { | 174 | mod tests { |
297 | use ra_db::salsa::InternKey; | 175 | use ra_db::SourceDatabase; |
298 | use ra_syntax::{SourceFile, algo::find_node_at_offset}; | 176 | use ra_syntax::{algo::find_node_at_offset, AstNode, SyntaxNodePtr, ast}; |
299 | use test_utils::{extract_offset, assert_eq_text}; | 177 | use test_utils::{extract_offset, assert_eq_text}; |
300 | use crate::Function; | ||
301 | |||
302 | use crate::expr::{ExprCollector}; | ||
303 | 178 | ||
304 | use super::*; | 179 | use crate::{source_binder::SourceAnalyzer, mock::MockDatabase}; |
305 | 180 | ||
306 | fn do_check(code: &str, expected: &[&str]) { | 181 | fn do_check(code: &str, expected: &[&str]) { |
307 | let (off, code) = extract_offset(code); | 182 | let (off, code) = extract_offset(code); |
@@ -313,18 +188,20 @@ mod tests { | |||
313 | buf.push_str(&code[off..]); | 188 | buf.push_str(&code[off..]); |
314 | buf | 189 | buf |
315 | }; | 190 | }; |
316 | let file = SourceFile::parse(&code); | 191 | |
192 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&code); | ||
193 | let file = db.parse(file_id); | ||
317 | let marker: &ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap(); | 194 | let marker: &ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap(); |
318 | let fn_def: &ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap(); | 195 | let analyzer = SourceAnalyzer::new(&db, file_id, marker.syntax(), None); |
319 | let irrelevant_function = | 196 | |
320 | Function { id: crate::ids::FunctionId::from_intern_id(0u32.into()) }; | 197 | let scopes = analyzer.scopes(); |
321 | let (body, source_map) = collect_fn_body_syntax(irrelevant_function, fn_def); | 198 | let expr_id = |
322 | let scopes = ExprScopes::new(Arc::new(body)); | 199 | analyzer.body_source_map().syntax_expr(SyntaxNodePtr::new(marker.syntax())).unwrap(); |
323 | let scopes = | 200 | let scope = scopes.scope_for(expr_id); |
324 | ScopesWithSourceMap { scopes: Arc::new(scopes), source_map: Arc::new(source_map) }; | 201 | |
325 | let actual = scopes | 202 | let actual = scopes |
326 | .scope_chain(marker.syntax()) | 203 | .scope_chain(scope) |
327 | .flat_map(|scope| scopes.scopes.entries(scope)) | 204 | .flat_map(|scope| scopes.entries(scope)) |
328 | .map(|it| it.name().to_string()) | 205 | .map(|it| it.name().to_string()) |
329 | .collect::<Vec<_>>() | 206 | .collect::<Vec<_>>() |
330 | .join("\n"); | 207 | .join("\n"); |
@@ -407,28 +284,17 @@ mod tests { | |||
407 | ); | 284 | ); |
408 | } | 285 | } |
409 | 286 | ||
410 | fn collect_fn_body_syntax(function: Function, node: &ast::FnDef) -> (Body, BodySourceMap) { | ||
411 | let mut collector = ExprCollector::new(DefWithBody::Function(function)); | ||
412 | collector.collect_fn_body(node); | ||
413 | collector.finish() | ||
414 | } | ||
415 | |||
416 | fn do_check_local_name(code: &str, expected_offset: u32) { | 287 | fn do_check_local_name(code: &str, expected_offset: u32) { |
417 | let (off, code) = extract_offset(code); | 288 | let (off, code) = extract_offset(code); |
418 | let file = SourceFile::parse(&code); | 289 | |
290 | let (db, _source_root, file_id) = MockDatabase::with_single_file(&code); | ||
291 | let file = db.parse(file_id); | ||
419 | let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()) | 292 | let expected_name = find_node_at_offset::<ast::Name>(file.syntax(), expected_offset.into()) |
420 | .expect("failed to find a name at the target offset"); | 293 | .expect("failed to find a name at the target offset"); |
421 | |||
422 | let fn_def: &ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap(); | ||
423 | let name_ref: &ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap(); | 294 | let name_ref: &ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap(); |
295 | let analyzer = SourceAnalyzer::new(&db, file_id, name_ref.syntax(), None); | ||
424 | 296 | ||
425 | let irrelevant_function = | 297 | let local_name_entry = analyzer.resolve_local_name(name_ref).unwrap(); |
426 | Function { id: crate::ids::FunctionId::from_intern_id(0u32.into()) }; | ||
427 | let (body, source_map) = collect_fn_body_syntax(irrelevant_function, fn_def); | ||
428 | let scopes = ExprScopes::new(Arc::new(body)); | ||
429 | let scopes = | ||
430 | ScopesWithSourceMap { scopes: Arc::new(scopes), source_map: Arc::new(source_map) }; | ||
431 | let local_name_entry = scopes.resolve_local_name(name_ref).unwrap(); | ||
432 | let local_name = | 298 | let local_name = |
433 | local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); | 299 | local_name_entry.ptr().either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); |
434 | assert_eq!(local_name.range(), expected_name.syntax().range()); | 300 | assert_eq!(local_name.range(), expected_name.syntax().range()); |