From 10d66d63d716a10ba7a5a8d1b69c9066249caf69 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov <aleksey.kladov@gmail.com> Date: Wed, 10 Apr 2019 11:15:55 +0300 Subject: introduce SourceAnalyzer --- crates/ra_ide_api/src/completion/complete_dot.rs | 11 +- .../src/completion/complete_struct_literal.rs | 11 +- .../src/completion/completion_context.rs | 3 + crates/ra_ide_api/src/display/navigation_target.rs | 25 +++- crates/ra_ide_api/src/goto_definition.rs | 152 +++++++-------------- crates/ra_ide_api/src/hover.rs | 18 ++- 6 files changed, 81 insertions(+), 139 deletions(-) (limited to 'crates/ra_ide_api/src') diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs index c093d5cfb..358057364 100644 --- a/crates/ra_ide_api/src/completion/complete_dot.rs +++ b/crates/ra_ide_api/src/completion/complete_dot.rs @@ -4,17 +4,10 @@ use crate::completion::{CompletionContext, Completions}; /// Complete dot accesses, i.e. fields or methods (currently only fields). pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { - let (function, receiver) = match (&ctx.function, ctx.dot_receiver) { - (Some(function), Some(receiver)) => (function, receiver), - _ => return, - }; - let infer_result = function.infer(ctx.db); - let source_map = function.body_source_map(ctx.db); - let expr = match source_map.node_expr(receiver) { - Some(expr) => expr, + let receiver_ty = match ctx.dot_receiver.and_then(|it| ctx.analyzer.type_of(ctx.db, it)) { + Some(it) => it, None => return, }; - let receiver_ty = infer_result[expr].clone(); if !ctx.is_call { complete_fields(acc, ctx, receiver_ty.clone()); } diff --git a/crates/ra_ide_api/src/completion/complete_struct_literal.rs b/crates/ra_ide_api/src/completion/complete_struct_literal.rs index f58bcd03e..48fbf67f7 100644 --- a/crates/ra_ide_api/src/completion/complete_struct_literal.rs +++ b/crates/ra_ide_api/src/completion/complete_struct_literal.rs @@ -4,17 +4,10 @@ use crate::completion::{CompletionContext, Completions}; /// Complete fields in fields literals. pub(super) fn complete_struct_literal(acc: &mut Completions, ctx: &CompletionContext) { - let (function, struct_lit) = match (&ctx.function, ctx.struct_lit_syntax) { - (Some(function), Some(struct_lit)) => (function, struct_lit), - _ => return, - }; - let infer_result = function.infer(ctx.db); - let source_map = function.body_source_map(ctx.db); - let expr = match source_map.node_expr(struct_lit.into()) { - Some(expr) => expr, + let ty = match ctx.struct_lit_syntax.and_then(|it| ctx.analyzer.type_of(ctx.db, it.into())) { + Some(it) => it, None => return, }; - let ty = infer_result[expr].clone(); let (adt, substs) = match ty.as_adt() { Some(res) => res, _ => return, diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs index 65dffa470..ce21fca9b 100644 --- a/crates/ra_ide_api/src/completion/completion_context.rs +++ b/crates/ra_ide_api/src/completion/completion_context.rs @@ -14,6 +14,7 @@ use crate::{db, FilePosition}; #[derive(Debug)] pub(crate) struct CompletionContext<'a> { pub(super) db: &'a db::RootDatabase, + pub(super) analyzer: hir::SourceAnalyser, pub(super) offset: TextUnit, pub(super) token: SyntaxToken<'a>, pub(super) resolver: Resolver, @@ -50,8 +51,10 @@ impl<'a> CompletionContext<'a> { let resolver = source_binder::resolver_for_position(db, position); let module = source_binder::module_from_position(db, position); let token = find_token_at_offset(original_file.syntax(), position.offset).left_biased()?; + let analyzer = hir::SourceAnalyser::new(db, position.file_id, token.parent()); let mut ctx = CompletionContext { db, + analyzer, token, offset: position.offset, resolver, diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs index 3c518faf5..84645287d 100644 --- a/crates/ra_ide_api/src/display/navigation_target.rs +++ b/crates/ra_ide_api/src/display/navigation_target.rs @@ -1,11 +1,11 @@ use ra_db::{FileId, SourceDatabase}; use ra_syntax::{ - SyntaxNode, SyntaxNodePtr, AstNode, SmolStr, TextRange, TreeArc, + SyntaxNode, AstNode, SmolStr, TextRange, TreeArc, AstPtr, SyntaxKind::{self, NAME}, ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner}, algo::visit::{visitor, Visitor}, }; -use hir::{ModuleSource, FieldSource, Name, ImplItem}; +use hir::{ModuleSource, FieldSource, ImplItem, Either}; use crate::{FileSymbol, db::RootDatabase}; @@ -74,15 +74,25 @@ impl NavigationTarget { } } - pub(crate) fn from_scope_entry( + pub(crate) fn from_pat( + db: &RootDatabase, file_id: FileId, - name: Name, - ptr: SyntaxNodePtr, + pat: Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>, ) -> NavigationTarget { + let file = db.parse(file_id); + let (name, full_range) = match pat { + Either::A(pat) => match pat.to_node(&file).kind() { + ast::PatKind::BindPat(pat) => { + return NavigationTarget::from_bind_pat(file_id, &pat) + } + _ => ("_".into(), pat.syntax_node_ptr().range()), + }, + Either::B(slf) => ("self".into(), slf.syntax_node_ptr().range()), + }; NavigationTarget { file_id, - name: name.to_string().into(), - full_range: ptr.range(), + name, + full_range, focus_range: None, kind: NAME, container_name: None, @@ -229,6 +239,7 @@ impl NavigationTarget { /// Allows `NavigationTarget` to be created from a `NameOwner` pub(crate) fn from_named(file_id: FileId, node: &impl ast::NameOwner) -> NavigationTarget { + //FIXME: use `_` instead of empty string let name = node.name().map(|it| it.text().clone()).unwrap_or_default(); let focus_range = node.name().map(|it| it.syntax().range()); NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax()) diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index 60c1f5085..7f93f50c4 100644 --- a/crates/ra_ide_api/src/goto_definition.rs +++ b/crates/ra_ide_api/src/goto_definition.rs @@ -5,7 +5,6 @@ use ra_syntax::{ SyntaxNode, }; use test_utils::tested_by; -use hir::Resolution; use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo}; @@ -48,127 +47,72 @@ pub(crate) fn reference_definition( ) -> ReferenceResult { use self::ReferenceResult::*; - let function = hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax()); - - if let Some(function) = function { - // Check if it is a method - if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { - tested_by!(goto_definition_works_for_methods); - let infer_result = function.infer(db); - let source_map = function.body_source_map(db); - let expr = ast::Expr::cast(method_call.syntax()).unwrap(); - if let Some(func) = - source_map.node_expr(expr).and_then(|it| infer_result.method_resolution(it)) - { - return Exact(NavigationTarget::from_function(db, func)); - }; - } - // It could also be a field access - if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { - tested_by!(goto_definition_works_for_fields); - let infer_result = function.infer(db); - let source_map = function.body_source_map(db); - let expr = ast::Expr::cast(field_expr.syntax()).unwrap(); - if let Some(field) = - source_map.node_expr(expr).and_then(|it| infer_result.field_resolution(it)) - { - return Exact(NavigationTarget::from_field(db, field)); - }; - } + let analyzer = hir::SourceAnalyser::new(db, file_id, name_ref.syntax()); - // It could also be a named field - if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) { - tested_by!(goto_definition_works_for_named_fields); + // Special cases: - let infer_result = function.infer(db); - let source_map = function.body_source_map(db); + // Check if it is a method + if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { + tested_by!(goto_definition_works_for_methods); + if let Some(func) = analyzer.resolve_method_call(method_call) { + return Exact(NavigationTarget::from_function(db, func)); + } + } + // It could also be a field access + if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { + tested_by!(goto_definition_works_for_fields); + if let Some(field) = analyzer.resolve_field(field_expr) { + return Exact(NavigationTarget::from_field(db, field)); + }; + } - let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); + // It could also be a named field + if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) { + tested_by!(goto_definition_works_for_named_fields); - if let Some(expr) = struct_lit.and_then(|lit| source_map.node_expr(lit.into())) { - let ty = infer_result[expr].clone(); - if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() { - let hir_path = hir::Path::from_name_ref(name_ref); - let hir_name = hir_path.as_ident().unwrap(); + let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); - if let Some(field) = s.field(db, hir_name) { - return Exact(NavigationTarget::from_field(db, field)); - } + if let Some(ty) = struct_lit.and_then(|lit| analyzer.type_of(db, lit.into())) { + if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() { + let hir_path = hir::Path::from_name_ref(name_ref); + let hir_name = hir_path.as_ident().unwrap(); + + if let Some(field) = s.field(db, hir_name) { + return Exact(NavigationTarget::from_field(db, field)); } } } } - // Try name resolution - let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax()); - if let Some(path) = - name_ref.syntax().ancestors().find_map(ast::Path::cast).and_then(hir::Path::from_ast) - { - let resolved = resolver.resolve_path(db, &path); - match resolved.clone().take_types().or_else(|| resolved.take_values()) { - Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)), - Some(Resolution::LocalBinding(pat)) => { - let body = resolver.body().expect("no body for local binding"); - let source_map = body.owner().body_source_map(db); - let ptr = source_map.pat_syntax(pat).expect("pattern not found in syntax mapping"); - let name = - path.as_ident().cloned().expect("local binding from a multi-segment path"); - let ptr = ptr.either(|it| it.into(), |it| it.into()); - let nav = NavigationTarget::from_scope_entry(file_id, name, ptr); - return Exact(nav); - } - Some(Resolution::GenericParam(..)) => { - // FIXME: go to the generic param def - } - Some(Resolution::SelfType(impl_block)) => { - let ty = impl_block.target_ty(db); - - if let Some((def_id, _)) = ty.as_adt() { - return Exact(NavigationTarget::from_adt_def(db, def_id)); - } - } - None => { - // If we failed to resolve then check associated items - if let Some(function) = function { - // Resolve associated item for path expressions - if let Some(path_expr) = - name_ref.syntax().ancestors().find_map(ast::PathExpr::cast) - { - let infer_result = function.infer(db); - let source_map = function.body_source_map(db); - - if let Some(expr) = ast::Expr::cast(path_expr.syntax()) { - if let Some(res) = source_map - .node_expr(expr) - .and_then(|it| infer_result.assoc_resolutions_for_expr(it.into())) - { - return Exact(NavigationTarget::from_impl_item(db, res)); - } - } + // General case, a path or a local: + if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { + if let Some(resolved) = analyzer.resolve_path(db, path) { + match resolved { + hir::PathResolution::Def(def) => return Exact(NavigationTarget::from_def(db, def)), + hir::PathResolution::LocalBinding(pat) => { + if let Some(pat) = analyzer.pat_syntax(db, pat) { + let nav = NavigationTarget::from_pat(db, file_id, pat); + return Exact(nav); } + } + hir::PathResolution::GenericParam(..) => { + // FIXME: go to the generic param def + } + hir::PathResolution::SelfType(impl_block) => { + let ty = impl_block.target_ty(db); - // Resolve associated item for path patterns - if let Some(path_pat) = - name_ref.syntax().ancestors().find_map(ast::PathPat::cast) - { - let infer_result = function.infer(db); - let source_map = function.body_source_map(db); - - let pat: &ast::Pat = path_pat.into(); - - if let Some(res) = source_map - .node_pat(pat) - .and_then(|it| infer_result.assoc_resolutions_for_pat(it.into())) - { - return Exact(NavigationTarget::from_impl_item(db, res)); - } + if let Some((def_id, _)) = ty.as_adt() { + return Exact(NavigationTarget::from_adt_def(db, def_id)); } } + hir::PathResolution::AssocItem(assoc) => { + return Exact(NavigationTarget::from_impl_item(db, assoc)) + } } } } - // If that fails try the index based approach. + // Fallback index based approach: let navs = crate::symbol_index::index_resolve(db, name_ref) .into_iter() .map(NavigationTarget::from_symbol) diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 3a8c93b99..ec167a196 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -132,17 +132,15 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { .ancestors() .take_while(|it| it.range() == leaf_node.range()) .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some())?; - let parent_fn = node.ancestors().find_map(ast::FnDef::cast)?; - let function = hir::source_binder::function_from_source(db, frange.file_id, parent_fn)?; - let infer = function.infer(db); - let source_map = function.body_source_map(db); - if let Some(expr) = ast::Expr::cast(node).and_then(|e| source_map.node_expr(e)) { - Some(infer[expr].display(db).to_string()) - } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| source_map.node_pat(p)) { - Some(infer[pat].display(db).to_string()) + let analyzer = hir::SourceAnalyser::new(db, frange.file_id, node); + let ty = if let Some(ty) = ast::Expr::cast(node).and_then(|e| analyzer.type_of(db, e)) { + ty + } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, p)) { + ty } else { - None - } + return None; + }; + Some(ty.display(db).to_string()) } #[cfg(test)] -- cgit v1.2.3