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