aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/source_binder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir/src/source_binder.rs')
-rw-r--r--crates/ra_hir/src/source_binder.rs406
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".
8use std::sync::Arc;
9
10use rustc_hash::{FxHashSet, FxHashMap};
8use ra_db::{FileId, FilePosition}; 11use ra_db::{FileId, FilePosition};
9use ra_syntax::{ 12use 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
15use crate::{ 19use 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
90pub 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
100pub 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
111pub 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
117pub 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
127pub 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
138pub 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
147pub fn struct_from_module( 95pub 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
158pub 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
168pub 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
179pub fn enum_from_module(db: &impl HirDatabase, module: Module, enum_def: &ast::EnumDef) -> Enum { 106pub 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
197pub 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
220pub 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
239fn try_get_resolver_for_node( 124fn 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
147fn 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)]
171pub 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)]
179pub 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)]
191pub struct ScopeEntryWithSyntax {
192 pub(crate) name: Name,
193 pub(crate) ptr: Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>,
194}
195
196impl 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)]
207pub struct ReferenceDescriptor {
208 pub range: TextRange,
209 pub name: String,
210}
211
212impl 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
362fn 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
373fn 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...
391fn 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}