diff options
Diffstat (limited to 'crates/ra_hir/src/source_binder.rs')
-rw-r--r-- | crates/ra_hir/src/source_binder.rs | 406 |
1 files changed, 281 insertions, 125 deletions
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 182ed4c91..bd035ced9 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs | |||
@@ -5,16 +5,21 @@ | |||
5 | /// | 5 | /// |
6 | /// So, this modules should not be used during hir construction, it exists | 6 | /// So, this modules should not be used during hir construction, it exists |
7 | /// purely for "IDE needs". | 7 | /// purely for "IDE needs". |
8 | use std::sync::Arc; | ||
9 | |||
10 | use rustc_hash::{FxHashSet, FxHashMap}; | ||
8 | use ra_db::{FileId, FilePosition}; | 11 | use ra_db::{FileId, FilePosition}; |
9 | use ra_syntax::{ | 12 | use ra_syntax::{ |
10 | SyntaxNode, | 13 | SyntaxNode, AstPtr, TextUnit, SyntaxNodePtr, TextRange, |
11 | ast::{self, AstNode, NameOwner}, | 14 | ast::{self, AstNode, NameOwner}, |
12 | algo::{find_node_at_offset, find_token_at_offset}, | 15 | algo::find_node_at_offset, |
16 | SyntaxKind::*, | ||
13 | }; | 17 | }; |
14 | 18 | ||
15 | use crate::{ | 19 | use crate::{ |
16 | HirDatabase, Function, Struct, Enum,Const,Static, | 20 | HirDatabase, Function, Struct, Enum, Const, Static, Either, DefWithBody, PerNs, Name, |
17 | AsName, Module, HirFileId, Crate, Trait, Resolver, | 21 | AsName, Module, HirFileId, Crate, Trait, Resolver, |
22 | expr::{BodySourceMap, scope::{ScopeId, ExprScopes}}, | ||
18 | ids::LocationCtx, | 23 | ids::LocationCtx, |
19 | expr, AstId | 24 | expr, AstId |
20 | }; | 25 | }; |
@@ -87,63 +92,6 @@ fn module_from_source( | |||
87 | ) | 92 | ) |
88 | } | 93 | } |
89 | 94 | ||
90 | pub fn const_from_source( | ||
91 | db: &impl HirDatabase, | ||
92 | file_id: FileId, | ||
93 | const_def: &ast::ConstDef, | ||
94 | ) -> Option<Const> { | ||
95 | let module = module_from_child_node(db, file_id, const_def.syntax())?; | ||
96 | let res = const_from_module(db, module, const_def); | ||
97 | Some(res) | ||
98 | } | ||
99 | |||
100 | pub fn const_from_module( | ||
101 | db: &impl HirDatabase, | ||
102 | module: Module, | ||
103 | const_def: &ast::ConstDef, | ||
104 | ) -> Const { | ||
105 | let (file_id, _) = module.definition_source(db); | ||
106 | let file_id = file_id.into(); | ||
107 | let ctx = LocationCtx::new(db, module, file_id); | ||
108 | Const { id: ctx.to_def(const_def) } | ||
109 | } | ||
110 | |||
111 | pub fn function_from_position(db: &impl HirDatabase, position: FilePosition) -> Option<Function> { | ||
112 | let file = db.parse(position.file_id); | ||
113 | let fn_def = find_node_at_offset::<ast::FnDef>(file.syntax(), position.offset)?; | ||
114 | function_from_source(db, position.file_id, fn_def) | ||
115 | } | ||
116 | |||
117 | pub fn function_from_source( | ||
118 | db: &impl HirDatabase, | ||
119 | file_id: FileId, | ||
120 | fn_def: &ast::FnDef, | ||
121 | ) -> Option<Function> { | ||
122 | let module = module_from_child_node(db, file_id, fn_def.syntax())?; | ||
123 | let res = function_from_module(db, module, fn_def); | ||
124 | Some(res) | ||
125 | } | ||
126 | |||
127 | pub fn function_from_module( | ||
128 | db: &impl HirDatabase, | ||
129 | module: Module, | ||
130 | fn_def: &ast::FnDef, | ||
131 | ) -> Function { | ||
132 | let (file_id, _) = module.definition_source(db); | ||
133 | let file_id = file_id.into(); | ||
134 | let ctx = LocationCtx::new(db, module, file_id); | ||
135 | Function { id: ctx.to_def(fn_def) } | ||
136 | } | ||
137 | |||
138 | pub fn function_from_child_node( | ||
139 | db: &impl HirDatabase, | ||
140 | file_id: FileId, | ||
141 | node: &SyntaxNode, | ||
142 | ) -> Option<Function> { | ||
143 | let fn_def = node.ancestors().find_map(ast::FnDef::cast)?; | ||
144 | function_from_source(db, file_id, fn_def) | ||
145 | } | ||
146 | |||
147 | pub fn struct_from_module( | 95 | pub fn struct_from_module( |
148 | db: &impl HirDatabase, | 96 | db: &impl HirDatabase, |
149 | module: Module, | 97 | module: Module, |
@@ -155,27 +103,6 @@ pub fn struct_from_module( | |||
155 | Struct { id: ctx.to_def(struct_def) } | 103 | Struct { id: ctx.to_def(struct_def) } |
156 | } | 104 | } |
157 | 105 | ||
158 | pub fn static_from_source( | ||
159 | db: &impl HirDatabase, | ||
160 | file_id: FileId, | ||
161 | static_def: &ast::StaticDef, | ||
162 | ) -> Option<Static> { | ||
163 | let module = module_from_child_node(db, file_id, static_def.syntax())?; | ||
164 | let res = static_from_module(db, module, static_def); | ||
165 | Some(res) | ||
166 | } | ||
167 | |||
168 | pub fn static_from_module( | ||
169 | db: &impl HirDatabase, | ||
170 | module: Module, | ||
171 | static_def: &ast::StaticDef, | ||
172 | ) -> Static { | ||
173 | let (file_id, _) = module.definition_source(db); | ||
174 | let file_id = file_id.into(); | ||
175 | let ctx = LocationCtx::new(db, module, file_id); | ||
176 | Static { id: ctx.to_def(static_def) } | ||
177 | } | ||
178 | |||
179 | pub fn enum_from_module(db: &impl HirDatabase, module: Module, enum_def: &ast::EnumDef) -> Enum { | 106 | pub fn enum_from_module(db: &impl HirDatabase, module: Module, enum_def: &ast::EnumDef) -> Enum { |
180 | let (file_id, _) = module.definition_source(db); | 107 | let (file_id, _) = module.definition_source(db); |
181 | let file_id = file_id.into(); | 108 | let file_id = file_id.into(); |
@@ -194,48 +121,6 @@ pub fn trait_from_module( | |||
194 | Trait { id: ctx.to_def(trait_def) } | 121 | Trait { id: ctx.to_def(trait_def) } |
195 | } | 122 | } |
196 | 123 | ||
197 | pub fn resolver_for_position(db: &impl HirDatabase, position: FilePosition) -> Resolver { | ||
198 | let file_id = position.file_id; | ||
199 | let file = db.parse(file_id); | ||
200 | find_token_at_offset(file.syntax(), position.offset) | ||
201 | .find_map(|token| { | ||
202 | token.parent().ancestors().find_map(|node| { | ||
203 | if ast::Expr::cast(node).is_some() || ast::Block::cast(node).is_some() { | ||
204 | if let Some(func) = function_from_child_node(db, file_id, node) { | ||
205 | let scopes = func.scopes(db); | ||
206 | let scope = scopes.scope_for_offset(position.offset); | ||
207 | Some(expr::resolver_for_scope(func.body(db), db, scope)) | ||
208 | } else { | ||
209 | // FIXME const/static/array length | ||
210 | None | ||
211 | } | ||
212 | } else { | ||
213 | try_get_resolver_for_node(db, file_id, node) | ||
214 | } | ||
215 | }) | ||
216 | }) | ||
217 | .unwrap_or_default() | ||
218 | } | ||
219 | |||
220 | pub fn resolver_for_node(db: &impl HirDatabase, file_id: FileId, node: &SyntaxNode) -> Resolver { | ||
221 | node.ancestors() | ||
222 | .find_map(|node| { | ||
223 | if ast::Expr::cast(node).is_some() || ast::Block::cast(node).is_some() { | ||
224 | if let Some(func) = function_from_child_node(db, file_id, node) { | ||
225 | let scopes = func.scopes(db); | ||
226 | let scope = scopes.scope_for(&node); | ||
227 | Some(expr::resolver_for_scope(func.body(db), db, scope)) | ||
228 | } else { | ||
229 | // FIXME const/static/array length | ||
230 | None | ||
231 | } | ||
232 | } else { | ||
233 | try_get_resolver_for_node(db, file_id, node) | ||
234 | } | ||
235 | }) | ||
236 | .unwrap_or_default() | ||
237 | } | ||
238 | |||
239 | fn try_get_resolver_for_node( | 124 | fn try_get_resolver_for_node( |
240 | db: &impl HirDatabase, | 125 | db: &impl HirDatabase, |
241 | file_id: FileId, | 126 | file_id: FileId, |
@@ -251,10 +136,281 @@ fn try_get_resolver_for_node( | |||
251 | } else if let Some(e) = ast::EnumDef::cast(node) { | 136 | } else if let Some(e) = ast::EnumDef::cast(node) { |
252 | let module = module_from_child_node(db, file_id, e.syntax())?; | 137 | let module = module_from_child_node(db, file_id, e.syntax())?; |
253 | Some(enum_from_module(db, module, e).resolver(db)) | 138 | Some(enum_from_module(db, module, e).resolver(db)) |
254 | } else if let Some(f) = ast::FnDef::cast(node) { | 139 | } else if node.kind() == FN_DEF || node.kind() == CONST_DEF || node.kind() == STATIC_DEF { |
255 | function_from_source(db, file_id, f).map(|f| f.resolver(db)) | 140 | Some(def_with_body_from_child_node(db, file_id, node)?.resolver(db)) |
256 | } else { | 141 | } else { |
257 | // FIXME add missing cases | 142 | // FIXME add missing cases |
258 | None | 143 | None |
259 | } | 144 | } |
260 | } | 145 | } |
146 | |||
147 | fn def_with_body_from_child_node( | ||
148 | db: &impl HirDatabase, | ||
149 | file_id: FileId, | ||
150 | node: &SyntaxNode, | ||
151 | ) -> Option<DefWithBody> { | ||
152 | let module = module_from_child_node(db, file_id, node)?; | ||
153 | let ctx = LocationCtx::new(db, module, file_id.into()); | ||
154 | node.ancestors().find_map(|node| { | ||
155 | if let Some(def) = ast::FnDef::cast(node) { | ||
156 | return Some(Function { id: ctx.to_def(def) }.into()); | ||
157 | } | ||
158 | if let Some(def) = ast::ConstDef::cast(node) { | ||
159 | return Some(Const { id: ctx.to_def(def) }.into()); | ||
160 | } | ||
161 | if let Some(def) = ast::StaticDef::cast(node) { | ||
162 | return Some(Static { id: ctx.to_def(def) }.into()); | ||
163 | } | ||
164 | None | ||
165 | }) | ||
166 | } | ||
167 | |||
168 | /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of | ||
169 | /// original source files. It should not be used inside the HIR itself. | ||
170 | #[derive(Debug)] | ||
171 | pub struct SourceAnalyzer { | ||
172 | resolver: Resolver, | ||
173 | body_source_map: Option<Arc<BodySourceMap>>, | ||
174 | infer: Option<Arc<crate::ty::InferenceResult>>, | ||
175 | scopes: Option<Arc<crate::expr::ExprScopes>>, | ||
176 | } | ||
177 | |||
178 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
179 | pub enum PathResolution { | ||
180 | /// An item | ||
181 | Def(crate::ModuleDef), | ||
182 | /// A local binding (only value namespace) | ||
183 | LocalBinding(Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>), | ||
184 | /// A generic parameter | ||
185 | GenericParam(u32), | ||
186 | SelfType(crate::ImplBlock), | ||
187 | AssocItem(crate::ImplItem), | ||
188 | } | ||
189 | |||
190 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
191 | pub struct ScopeEntryWithSyntax { | ||
192 | pub(crate) name: Name, | ||
193 | pub(crate) ptr: Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>, | ||
194 | } | ||
195 | |||
196 | impl ScopeEntryWithSyntax { | ||
197 | pub fn name(&self) -> &Name { | ||
198 | &self.name | ||
199 | } | ||
200 | |||
201 | pub fn ptr(&self) -> Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>> { | ||
202 | self.ptr | ||
203 | } | ||
204 | } | ||
205 | |||
206 | #[derive(Debug)] | ||
207 | pub struct ReferenceDescriptor { | ||
208 | pub range: TextRange, | ||
209 | pub name: String, | ||
210 | } | ||
211 | |||
212 | impl SourceAnalyzer { | ||
213 | pub fn new( | ||
214 | db: &impl HirDatabase, | ||
215 | file_id: FileId, | ||
216 | node: &SyntaxNode, | ||
217 | offset: Option<TextUnit>, | ||
218 | ) -> SourceAnalyzer { | ||
219 | let def_with_body = def_with_body_from_child_node(db, file_id, node); | ||
220 | if let Some(def) = def_with_body { | ||
221 | let source_map = def.body_source_map(db); | ||
222 | let scopes = db.expr_scopes(def); | ||
223 | let scope = match offset { | ||
224 | None => scope_for(&scopes, &source_map, &node), | ||
225 | Some(offset) => scope_for_offset(&scopes, &source_map, offset), | ||
226 | }; | ||
227 | let resolver = expr::resolver_for_scope(def.body(db), db, scope); | ||
228 | SourceAnalyzer { | ||
229 | resolver, | ||
230 | body_source_map: Some(source_map), | ||
231 | infer: Some(def.infer(db)), | ||
232 | scopes: Some(scopes), | ||
233 | } | ||
234 | } else { | ||
235 | SourceAnalyzer { | ||
236 | resolver: node | ||
237 | .ancestors() | ||
238 | .find_map(|node| try_get_resolver_for_node(db, file_id, node)) | ||
239 | .unwrap_or_default(), | ||
240 | body_source_map: None, | ||
241 | infer: None, | ||
242 | scopes: None, | ||
243 | } | ||
244 | } | ||
245 | } | ||
246 | |||
247 | pub fn type_of(&self, _db: &impl HirDatabase, expr: &ast::Expr) -> Option<crate::Ty> { | ||
248 | let expr_id = self.body_source_map.as_ref()?.node_expr(expr)?; | ||
249 | Some(self.infer.as_ref()?[expr_id].clone()) | ||
250 | } | ||
251 | |||
252 | pub fn type_of_pat(&self, _db: &impl HirDatabase, pat: &ast::Pat) -> Option<crate::Ty> { | ||
253 | let pat_id = self.body_source_map.as_ref()?.node_pat(pat)?; | ||
254 | Some(self.infer.as_ref()?[pat_id].clone()) | ||
255 | } | ||
256 | |||
257 | pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> { | ||
258 | let expr_id = self.body_source_map.as_ref()?.node_expr(call.into())?; | ||
259 | self.infer.as_ref()?.method_resolution(expr_id) | ||
260 | } | ||
261 | |||
262 | pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option<crate::StructField> { | ||
263 | let expr_id = self.body_source_map.as_ref()?.node_expr(field.into())?; | ||
264 | self.infer.as_ref()?.field_resolution(expr_id) | ||
265 | } | ||
266 | |||
267 | pub fn resolve_hir_path( | ||
268 | &self, | ||
269 | db: &impl HirDatabase, | ||
270 | path: &crate::Path, | ||
271 | ) -> PerNs<crate::Resolution> { | ||
272 | self.resolver.resolve_path(db, path) | ||
273 | } | ||
274 | |||
275 | pub fn resolve_path(&self, db: &impl HirDatabase, path: &ast::Path) -> Option<PathResolution> { | ||
276 | if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
277 | let expr_id = self.body_source_map.as_ref()?.node_expr(path_expr.into())?; | ||
278 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { | ||
279 | return Some(PathResolution::AssocItem(assoc)); | ||
280 | } | ||
281 | } | ||
282 | if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { | ||
283 | let pat_id = self.body_source_map.as_ref()?.node_pat(path_pat.into())?; | ||
284 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { | ||
285 | return Some(PathResolution::AssocItem(assoc)); | ||
286 | } | ||
287 | } | ||
288 | let hir_path = crate::Path::from_ast(path)?; | ||
289 | let res = self.resolver.resolve_path(db, &hir_path); | ||
290 | let res = res.clone().take_types().or_else(|| res.take_values())?; | ||
291 | let res = match res { | ||
292 | crate::Resolution::Def(it) => PathResolution::Def(it), | ||
293 | crate::Resolution::LocalBinding(it) => { | ||
294 | PathResolution::LocalBinding(self.body_source_map.as_ref()?.pat_syntax(it)?) | ||
295 | } | ||
296 | crate::Resolution::GenericParam(it) => PathResolution::GenericParam(it), | ||
297 | crate::Resolution::SelfType(it) => PathResolution::SelfType(it), | ||
298 | }; | ||
299 | Some(res) | ||
300 | } | ||
301 | |||
302 | pub fn resolve_local_name(&self, name_ref: &ast::NameRef) -> Option<ScopeEntryWithSyntax> { | ||
303 | let mut shadowed = FxHashSet::default(); | ||
304 | let name = name_ref.as_name(); | ||
305 | let source_map = self.body_source_map.as_ref()?; | ||
306 | let scopes = self.scopes.as_ref()?; | ||
307 | let scope = scope_for(scopes, source_map, name_ref.syntax()); | ||
308 | let ret = scopes | ||
309 | .scope_chain(scope) | ||
310 | .flat_map(|scope| scopes.entries(scope).iter()) | ||
311 | .filter(|entry| shadowed.insert(entry.name())) | ||
312 | .filter(|entry| entry.name() == &name) | ||
313 | .nth(0); | ||
314 | ret.and_then(|entry| { | ||
315 | Some(ScopeEntryWithSyntax { | ||
316 | name: entry.name().clone(), | ||
317 | ptr: source_map.pat_syntax(entry.pat())?, | ||
318 | }) | ||
319 | }) | ||
320 | } | ||
321 | |||
322 | pub fn all_names(&self, db: &impl HirDatabase) -> FxHashMap<Name, PerNs<crate::Resolution>> { | ||
323 | self.resolver.all_names(db) | ||
324 | } | ||
325 | |||
326 | pub fn find_all_refs(&self, pat: &ast::BindPat) -> Vec<ReferenceDescriptor> { | ||
327 | // FIXME: at least, this should work with any DefWithBody, but ideally | ||
328 | // this should be hir-based altogether | ||
329 | let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); | ||
330 | let ptr = Either::A(AstPtr::new(pat.into())); | ||
331 | fn_def | ||
332 | .syntax() | ||
333 | .descendants() | ||
334 | .filter_map(ast::NameRef::cast) | ||
335 | .filter(|name_ref| match self.resolve_local_name(*name_ref) { | ||
336 | None => false, | ||
337 | Some(entry) => entry.ptr() == ptr, | ||
338 | }) | ||
339 | .map(|name_ref| ReferenceDescriptor { | ||
340 | name: name_ref.text().to_string(), | ||
341 | range: name_ref.syntax().range(), | ||
342 | }) | ||
343 | .collect() | ||
344 | } | ||
345 | |||
346 | #[cfg(test)] | ||
347 | pub(crate) fn body_source_map(&self) -> Arc<BodySourceMap> { | ||
348 | self.body_source_map.clone().unwrap() | ||
349 | } | ||
350 | |||
351 | #[cfg(test)] | ||
352 | pub(crate) fn inference_result(&self) -> Arc<crate::ty::InferenceResult> { | ||
353 | self.infer.clone().unwrap() | ||
354 | } | ||
355 | |||
356 | #[cfg(test)] | ||
357 | pub(crate) fn scopes(&self) -> Arc<ExprScopes> { | ||
358 | self.scopes.clone().unwrap() | ||
359 | } | ||
360 | } | ||
361 | |||
362 | fn scope_for( | ||
363 | scopes: &ExprScopes, | ||
364 | source_map: &BodySourceMap, | ||
365 | node: &SyntaxNode, | ||
366 | ) -> Option<ScopeId> { | ||
367 | node.ancestors() | ||
368 | .map(SyntaxNodePtr::new) | ||
369 | .filter_map(|ptr| source_map.syntax_expr(ptr)) | ||
370 | .find_map(|it| scopes.scope_for(it)) | ||
371 | } | ||
372 | |||
373 | fn scope_for_offset( | ||
374 | scopes: &ExprScopes, | ||
375 | source_map: &BodySourceMap, | ||
376 | offset: TextUnit, | ||
377 | ) -> Option<ScopeId> { | ||
378 | scopes | ||
379 | .scope_by_expr() | ||
380 | .iter() | ||
381 | .filter_map(|(id, scope)| Some((source_map.expr_syntax(*id)?, scope))) | ||
382 | // find containing scope | ||
383 | .min_by_key(|(ptr, _scope)| { | ||
384 | (!(ptr.range().start() <= offset && offset <= ptr.range().end()), ptr.range().len()) | ||
385 | }) | ||
386 | .map(|(ptr, scope)| adjust(scopes, source_map, ptr, offset).unwrap_or(*scope)) | ||
387 | } | ||
388 | |||
389 | // XXX: during completion, cursor might be outside of any particular | ||
390 | // expression. Try to figure out the correct scope... | ||
391 | fn adjust( | ||
392 | scopes: &ExprScopes, | ||
393 | source_map: &BodySourceMap, | ||
394 | ptr: SyntaxNodePtr, | ||
395 | offset: TextUnit, | ||
396 | ) -> Option<ScopeId> { | ||
397 | let r = ptr.range(); | ||
398 | let child_scopes = scopes | ||
399 | .scope_by_expr() | ||
400 | .iter() | ||
401 | .filter_map(|(id, scope)| Some((source_map.expr_syntax(*id)?, scope))) | ||
402 | .map(|(ptr, scope)| (ptr.range(), scope)) | ||
403 | .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r); | ||
404 | |||
405 | child_scopes | ||
406 | .max_by(|(r1, _), (r2, _)| { | ||
407 | if r2.is_subrange(&r1) { | ||
408 | std::cmp::Ordering::Greater | ||
409 | } else if r1.is_subrange(&r2) { | ||
410 | std::cmp::Ordering::Less | ||
411 | } else { | ||
412 | r1.start().cmp(&r2.start()) | ||
413 | } | ||
414 | }) | ||
415 | .map(|(_ptr, scope)| *scope) | ||
416 | } | ||