Repository: rust-analyzer/rust-analyzer
Pull request #438 (2019-01-05 17:58:33)

show types in local variable hovers


Patch:
diff --git a/crates/ra_analysis/src/hover.rs b/crates/ra_analysis/src/hover.rs
index 758de376e3..2cf79eebf1 100644
--- a/crates/ra_analysis/src/hover.rs
+++ b/crates/ra_analysis/src/hover.rs
@@ -14,23 +14,28 @@ pub(crate) fn hover(
 ) -> Cancelable<Option<RangeInfo<String>>> {
     let file = db.source_file(position.file_id);
     let mut res = Vec::new();
-    let range = if let Some(name_ref) =
-        find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset)
-    {
+
+    let mut range = None;
+    if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
         let navs = crate::goto_defenition::reference_defenition(db, position.file_id, name_ref)?;
         for nav in navs {
             res.extend(doc_text_for(db, nav)?)
         }
-        name_ref.syntax().range()
-    } else {
+        if !res.is_empty() {
+            range = Some(name_ref.syntax().range())
+        }
+    }
+    if range.is_none() {
         let expr: ast::Expr = ctry!(find_node_at_offset(file.syntax(), position.offset));
         let frange = FileRange {
             file_id: position.file_id,
             range: expr.syntax().range(),
         };
         res.extend(type_of(db, frange)?);
-        expr.syntax().range()
+        range = Some(expr.syntax().range());
     };
+
+    let range = ctry!(range);
     if res.is_empty() {
         return Ok(None);
     }
@@ -41,7 +46,13 @@ pub(crate) fn hover(
 pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Cancelable<Option<String>> {
     let file = db.source_file(frange.file_id);
     let syntax = file.syntax();
-    let node = find_covering_node(syntax, frange.range);
+    let leaf_node = find_covering_node(syntax, frange.range);
+    // if we picked identifier, expand to pattern/expression
+    let node = leaf_node
+        .ancestors()
+        .take_while(|it| it.range() == leaf_node.range())
+        .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some())
+        .unwrap_or(leaf_node);
     let parent_fn = ctry!(node.ancestors().find_map(ast::FnDef::cast));
     let function = ctry!(hir::source_binder::function_from_source(
         db,
@@ -156,8 +167,7 @@ impl NavigationTarget {
 #[cfg(test)]
 mod tests {
     use ra_syntax::TextRange;
-
-    use crate::mock_analysis::single_file_with_position;
+    use crate::mock_analysis::{single_file_with_position, single_file_with_range};
 
     #[test]
     fn hover_shows_type_of_an_expression() {
@@ -168,10 +178,65 @@ mod tests {
             fn main() {
                 let foo_test = foo()<|>;
             }
-        ",
+            ",
         );
         let hover = analysis.hover(position).unwrap().unwrap();
         assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
         assert_eq!(hover.info, "u32");
     }
+
+    #[test]
+    fn hover_for_local_variable() {
+        let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
+        let hover = analysis.hover(position).unwrap().unwrap();
+        assert_eq!(hover.info, "i32");
+    }
+
+    #[test]
+    fn test_type_of_for_function() {
+        let (analysis, range) = single_file_with_range(
+            "
+            pub fn foo() -> u32 { 1 };
+
+            fn main() {
+                let foo_test = <|>foo()<|>;
+            }
+            ",
+        );
+
+        let type_name = analysis.type_of(range).unwrap().unwrap();
+        assert_eq!("u32", &type_name);
+    }
+
+    // FIXME: improve type_of to make this work
+    #[test]
+    fn test_type_of_for_expr_1() {
+        let (analysis, range) = single_file_with_range(
+            "
+            fn main() {
+                let foo = <|>1 + foo_test<|>;
+            }
+            ",
+        );
+
+        let type_name = analysis.type_of(range).unwrap().unwrap();
+        assert_eq!("[unknown]", &type_name);
+    }
+
+    // FIXME: improve type_of to make this work
+    #[test]
+    fn test_type_of_for_expr_2() {
+        let (analysis, range) = single_file_with_range(
+            "
+            fn main() {
+                let foo: usize = 1;
+                let bar = <|>1 + foo_test<|>;
+            }
+            ",
+        );
+
+        let type_name = analysis.type_of(range).unwrap().unwrap();
+        assert_eq!("[unknown]", &type_name);
+    }
+
 }
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index 4d895b0044..390c31c3fa 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -228,7 +228,7 @@ impl Query {
 ///
 /// Typically, a `NavigationTarget` corresponds to some element in the source
 /// code, like a function or a struct, but this is not strictly required.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct NavigationTarget {
     file_id: FileId,
     name: SmolStr,


Test patch:
diff --git a/crates/ra_analysis/tests/test/main.rs b/crates/ra_analysis/tests/test/main.rs
index e15035304b..26da7c10c9 100644
--- a/crates/ra_analysis/tests/test/main.rs
+++ b/crates/ra_analysis/tests/test/main.rs
@@ -1,5 +1,4 @@
 mod runnables;
-mod type_of;
 
 use ra_syntax::TextRange;
 use test_utils::{assert_eq_dbg, assert_eq_text};
diff --git a/crates/ra_analysis/tests/test/type_of.rs b/crates/ra_analysis/tests/test/type_of.rs
deleted file mode 100644
index 9d15b52a88..0000000000
--- a/crates/ra_analysis/tests/test/type_of.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-use ra_analysis::mock_analysis::single_file_with_range;
-
-#[test]
-fn test_type_of_for_function() {
-    let (analysis, range) = single_file_with_range(
-        "
-        pub fn foo() -> u32 { 1 };
-
-        fn main() {
-            let foo_test = <|>foo()<|>;
-        }
-        ",
-    );
-
-    let type_name = analysis.type_of(range).unwrap().unwrap();
-    assert_eq!("u32", &type_name);
-}
-
-// FIXME: improve type_of to make this work
-#[test]
-fn test_type_of_for_num() {
-    let (analysis, range) = single_file_with_range(
-        r#"
-        fn main() {
-            let foo_test = <|>"foo"<|>;
-        }
-        "#,
-    );
-
-    assert!(analysis.type_of(range).unwrap().is_none());
-}
-// FIXME: improve type_of to make this work
-#[test]
-fn test_type_of_for_binding() {
-    let (analysis, range) = single_file_with_range(
-        "
-        pub fn foo() -> u32 { 1 };
-
-        fn main() {
-            let <|>foo_test<|> = foo();
-        }
-        ",
-    );
-
-    assert!(analysis.type_of(range).unwrap().is_none());
-}
-
-// FIXME: improve type_of to make this work
-#[test]
-fn test_type_of_for_expr_1() {
-    let (analysis, range) = single_file_with_range(
-        "
-        fn main() {
-            let foo = <|>1 + foo_test<|>;
-        }
-        ",
-    );
-
-    let type_name = analysis.type_of(range).unwrap().unwrap();
-    assert_eq!("[unknown]", &type_name);
-}
-
-// FIXME: improve type_of to make this work
-#[test]
-fn test_type_of_for_expr_2() {
-    let (analysis, range) = single_file_with_range(
-        "
-        fn main() {
-            let foo: usize = 1;
-            let bar = <|>1 + foo_test<|>;
-        }
-        ",
-    );
-
-    let type_name = analysis.type_of(range).unwrap().unwrap();
-    assert_eq!("[unknown]", &type_name);
-}