]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/call_hierarchy.rs
fix: Fix incorrect token pick rankings
[rust.git] / crates / ide / src / call_hierarchy.rs
index 96021f677f58959120c1fa49372b07206ea1bf7e..5a8cda8fb3dda19e0c4ea6c5c0f4b0fbc72073f9 100644 (file)
@@ -1,15 +1,15 @@
 //! Entry point for call-hierarchy
 
-use indexmap::IndexMap;
-
 use hir::Semantics;
-use ide_db::call_info::FnCallNode;
-use ide_db::RootDatabase;
-use syntax::{ast, AstNode, TextRange};
-
-use crate::{
-    display::TryToNav, goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
+use ide_db::{
+    defs::{Definition, NameClass, NameRefClass},
+    helpers::pick_best_token,
+    search::FileReference,
+    FxIndexMap, RootDatabase,
 };
+use syntax::{ast, AstNode, SyntaxKind::IDENT, TextRange};
+
+use crate::{goto_definition, FilePosition, NavigationTarget, RangeInfo, TryToNav};
 
 #[derive(Debug, Clone)]
 pub struct CallItem {
@@ -18,12 +18,6 @@ pub struct CallItem {
 }
 
 impl CallItem {
-    #[cfg(test)]
-    pub(crate) fn assert_match(&self, expected: &str) {
-        let actual = self.debug_render();
-        test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
-    }
-
     #[cfg(test)]
     pub(crate) fn debug_render(&self) -> String {
         format!("{} : {:?}", self.target.debug_render(), self.ranges)
@@ -37,30 +31,41 @@ pub(crate) fn call_hierarchy(
     goto_definition::goto_definition(db, position)
 }
 
-pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
-    let sema = Semantics::new(db);
-
-    // 1. Find all refs
-    // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
-    // 3. Add ranges relative to the start of the fndef.
-    let refs = references::find_all_refs(&sema, position, None)?;
+pub(crate) fn incoming_calls(
+    db: &RootDatabase,
+    FilePosition { file_id, offset }: FilePosition,
+) -> Option<Vec<CallItem>> {
+    let sema = &Semantics::new(db);
 
+    let file = sema.parse(file_id);
+    let file = file.syntax();
     let mut calls = CallLocations::default();
 
-    for (file_id, references) in refs.references {
-        let file = sema.parse(file_id);
-        let file = file.syntax();
-        for (r_range, _) in references {
-            let token = file.token_at_offset(r_range.start()).next()?;
-            let token = sema.descend_into_macros(token);
+    let references = sema
+        .find_nodes_at_offset_with_descend(file, offset)
+        .filter_map(move |node| match node {
+            ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
+                NameRefClass::Definition(def @ Definition::Function(_)) => Some(def),
+                _ => None,
+            },
+            ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
+                NameClass::Definition(def @ Definition::Function(_)) => Some(def),
+                _ => None,
+            },
+            ast::NameLike::Lifetime(_) => None,
+        })
+        .flat_map(|func| func.usages(sema).all());
+
+    for (_, references) in references {
+        let references = references.into_iter().map(|FileReference { name, .. }| name);
+        for name in references {
             // This target is the containing function
-            if let Some(nav) = token.ancestors().find_map(|node| {
-                let fn_ = ast::Fn::cast(node)?;
-                let def = sema.to_def(&fn_)?;
+            let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
+                let def = ast::Fn::cast(node).and_then(|fn_| sema.to_def(&fn_))?;
                 def.try_to_nav(sema.db)
-            }) {
-                let relative_range = r_range;
-                calls.add(&nav, relative_range);
+            });
+            if let Some(nav) = nav {
+                calls.add(nav, sema.original_range(name.syntax()).range);
             }
         }
     }
@@ -73,47 +78,57 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio
     let file_id = position.file_id;
     let file = sema.parse(file_id);
     let file = file.syntax();
-    let token = file.token_at_offset(position.offset).next()?;
-    let token = sema.descend_into_macros(token);
-
+    let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+        IDENT => 1,
+        _ => 0,
+    })?;
     let mut calls = CallLocations::default();
 
-    token
-        .parent()
+    sema.descend_into_macros(token)
         .into_iter()
-        .flat_map(|it| it.descendants())
-        .filter_map(|node| FnCallNode::with_node_exact(&node))
+        .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
+        .filter_map(|item| match item {
+            ast::Item::Const(c) => c.body().map(|it| it.syntax().descendants()),
+            ast::Item::Fn(f) => f.body().map(|it| it.syntax().descendants()),
+            ast::Item::Static(s) => s.body().map(|it| it.syntax().descendants()),
+            _ => None,
+        })
+        .flatten()
+        .filter_map(ast::CallableExpr::cast)
         .filter_map(|call_node| {
-            let name_ref = call_node.name_ref()?;
-            let func_target = match call_node {
-                FnCallNode::CallExpr(expr) => {
-                    //FIXME: Type::as_callable is broken
-                    let callable = sema.type_of_expr(&expr.expr()?)?.as_callable(db)?;
+            let (nav_target, range) = match call_node {
+                ast::CallableExpr::Call(call) => {
+                    let expr = call.expr()?;
+                    let callable = sema.type_of_expr(&expr)?.original.as_callable(db)?;
                     match callable.kind() {
-                        hir::CallableKind::Function(it) => it.try_to_nav(db),
+                        hir::CallableKind::Function(it) => {
+                            let range = expr.syntax().text_range();
+                            it.try_to_nav(db).zip(Some(range))
+                        }
                         _ => None,
                     }
                 }
-                FnCallNode::MethodCallExpr(expr) => {
+                ast::CallableExpr::MethodCall(expr) => {
+                    let range = expr.name_ref()?.syntax().text_range();
                     let function = sema.resolve_method_call(&expr)?;
-                    function.try_to_nav(db)
+                    function.try_to_nav(db).zip(Some(range))
                 }
             }?;
-            Some((func_target, name_ref.syntax().text_range()))
+            Some((nav_target, range))
         })
-        .for_each(|(nav, range)| calls.add(&nav, range));
+        .for_each(|(nav, range)| calls.add(nav, range));
 
     Some(calls.into_items())
 }
 
 #[derive(Default)]
 struct CallLocations {
-    funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
+    funcs: FxIndexMap<NavigationTarget, Vec<TextRange>>,
 }
 
 impl CallLocations {
-    fn add(&mut self, target: &NavigationTarget, range: TextRange) {
-        self.funcs.entry(target.clone()).or_default().push(range);
+    fn add(&mut self, target: NavigationTarget, range: TextRange) {
+        self.funcs.entry(target).or_default().push(range);
     }
 
     fn into_items(self) -> Vec<CallItem> {
@@ -123,38 +138,34 @@ fn into_items(self) -> Vec<CallItem> {
 
 #[cfg(test)]
 mod tests {
+    use expect_test::{expect, Expect};
     use ide_db::base_db::FilePosition;
+    use itertools::Itertools;
 
     use crate::fixture;
 
     fn check_hierarchy(
         ra_fixture: &str,
-        expected: &str,
-        expected_incoming: &[&str],
-        expected_outgoing: &[&str],
+        expected: Expect,
+        expected_incoming: Expect,
+        expected_outgoing: Expect,
     ) {
         let (analysis, pos) = fixture::position(ra_fixture);
 
         let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
         assert_eq!(navs.len(), 1);
         let nav = navs.pop().unwrap();
-        nav.assert_match(expected);
+        expected.assert_eq(&nav.debug_render());
 
         let item_pos =
             FilePosition { file_id: nav.file_id, offset: nav.focus_or_full_range().start() };
         let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
-        assert_eq!(incoming_calls.len(), expected_incoming.len());
-
-        for call in 0..incoming_calls.len() {
-            incoming_calls[call].assert_match(expected_incoming[call]);
-        }
+        expected_incoming
+            .assert_eq(&incoming_calls.into_iter().map(|call| call.debug_render()).join("\n"));
 
         let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
-        assert_eq!(outgoing_calls.len(), expected_outgoing.len());
-
-        for call in 0..outgoing_calls.len() {
-            outgoing_calls[call].assert_match(expected_outgoing[call]);
-        }
+        expected_outgoing
+            .assert_eq(&outgoing_calls.into_iter().map(|call| call.debug_render()).join("\n"));
     }
 
     #[test]
@@ -167,9 +178,9 @@ fn caller() {
     call$0ee();
 }
 "#,
-            "callee Function FileId(0) 0..14 3..9",
-            &["caller Function FileId(0) 15..44 18..24 : [33..39]"],
-            &[],
+            expect![["callee Function FileId(0) 0..14 3..9"]],
+            expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
+            expect![[]],
         );
     }
 
@@ -183,9 +194,9 @@ fn caller() {
     callee();
 }
 "#,
-            "callee Function FileId(0) 0..14 3..9",
-            &["caller Function FileId(0) 15..44 18..24 : [33..39]"],
-            &[],
+            expect![["callee Function FileId(0) 0..14 3..9"]],
+            expect![["caller Function FileId(0) 15..44 18..24 : [33..39]"]],
+            expect![[]],
         );
     }
 
@@ -200,9 +211,9 @@ fn caller() {
     callee();
 }
 "#,
-            "callee Function FileId(0) 0..14 3..9",
-            &["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"],
-            &[],
+            expect![["callee Function FileId(0) 0..14 3..9"]],
+            expect![["caller Function FileId(0) 15..58 18..24 : [33..39, 47..53]"]],
+            expect![[]],
         );
     }
 
@@ -220,12 +231,11 @@ fn caller2() {
     callee();
 }
 "#,
-            "callee Function FileId(0) 0..14 3..9",
-            &[
-                "caller1 Function FileId(0) 15..45 18..25 : [34..40]",
-                "caller2 Function FileId(0) 47..77 50..57 : [66..72]",
-            ],
-            &[],
+            expect![["callee Function FileId(0) 0..14 3..9"]],
+            expect![["
+                caller1 Function FileId(0) 15..45 18..25 : [34..40]
+                caller2 Function FileId(0) 47..77 50..57 : [66..72]"]],
+            expect![[]],
         );
     }
 
@@ -249,12 +259,11 @@ fn test_caller() {
     }
 }
 "#,
-            "callee Function FileId(0) 0..14 3..9",
-            &[
-                "caller1 Function FileId(0) 15..45 18..25 : [34..40]",
-                "test_caller Function FileId(0) 95..149 110..121 : [134..140]",
-            ],
-            &[],
+            expect![["callee Function FileId(0) 0..14 3..9"]],
+            expect![[r#"
+                caller1 Function FileId(0) 15..45 18..25 : [34..40]
+                test_caller Function FileId(0) 95..149 110..121 : [134..140]"#]],
+            expect![[]],
         );
     }
 
@@ -273,9 +282,9 @@ fn caller() {
 //- /foo/mod.rs
 pub fn callee() {}
 "#,
-            "callee Function FileId(1) 0..18 7..13",
-            &["caller Function FileId(0) 27..56 30..36 : [45..51]"],
-            &[],
+            expect![["callee Function FileId(1) 0..18 7..13"]],
+            expect![["caller Function FileId(0) 27..56 30..36 : [45..51]"]],
+            expect![[]],
         );
     }
 
@@ -290,9 +299,9 @@ fn call$0er() {
     callee();
 }
 "#,
-            "caller Function FileId(0) 15..58 18..24",
-            &[],
-            &["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"],
+            expect![["caller Function FileId(0) 15..58 18..24"]],
+            expect![[]],
+            expect![["callee Function FileId(0) 0..14 3..9 : [33..39, 47..53]"]],
         );
     }
 
@@ -311,9 +320,9 @@ fn call$0er() {
 //- /foo/mod.rs
 pub fn callee() {}
 "#,
-            "caller Function FileId(0) 27..56 30..36",
-            &[],
-            &["callee Function FileId(1) 0..18 7..13 : [45..51]"],
+            expect![["caller Function FileId(0) 27..56 30..36"]],
+            expect![[]],
+            expect![["callee Function FileId(1) 0..18 7..13 : [45..51]"]],
         );
     }
 
@@ -334,9 +343,9 @@ fn caller3() {
 
 }
 "#,
-            "caller2 Function FileId(0) 33..64 36..43",
-            &["caller1 Function FileId(0) 0..31 3..10 : [19..26]"],
-            &["caller3 Function FileId(0) 66..83 69..76 : [52..59]"],
+            expect![["caller2 Function FileId(0) 33..64 36..43"]],
+            expect![["caller1 Function FileId(0) 0..31 3..10 : [19..26]"]],
+            expect![["caller3 Function FileId(0) 66..83 69..76 : [52..59]"]],
         );
     }
 
@@ -354,9 +363,9 @@ fn main() {
     a$0()
 }
 "#,
-            "a Function FileId(0) 0..18 3..4",
-            &["main Function FileId(0) 31..52 34..38 : [47..48]"],
-            &["b Function FileId(0) 20..29 23..24 : [13..14]"],
+            expect![["a Function FileId(0) 0..18 3..4"]],
+            expect![["main Function FileId(0) 31..52 34..38 : [47..48]"]],
+            expect![["b Function FileId(0) 20..29 23..24 : [13..14]"]],
         );
 
         check_hierarchy(
@@ -371,9 +380,81 @@ fn main() {
     a()
 }
 "#,
-            "b Function FileId(0) 20..29 23..24",
-            &["a Function FileId(0) 0..18 3..4 : [13..14]"],
-            &[],
+            expect![["b Function FileId(0) 20..29 23..24"]],
+            expect![["a Function FileId(0) 0..18 3..4 : [13..14]"]],
+            expect![[]],
+        );
+    }
+
+    #[test]
+    fn test_call_hierarchy_in_macros_incoming() {
+        check_hierarchy(
+            r#"
+macro_rules! define {
+    ($ident:ident) => {
+        fn $ident {}
+    }
+}
+macro_rules! call {
+    ($ident:ident) => {
+        $ident()
+    }
+}
+define!(callee)
+fn caller() {
+    call!(call$0ee);
+}
+"#,
+            expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
+            expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
+            expect![[]],
+        );
+        check_hierarchy(
+            r#"
+macro_rules! define {
+    ($ident:ident) => {
+        fn $ident {}
+    }
+}
+macro_rules! call {
+    ($ident:ident) => {
+        $ident()
+    }
+}
+define!(cal$0lee)
+fn caller() {
+    call!(callee);
+}
+"#,
+            expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
+            expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
+            expect![[]],
+        );
+    }
+
+    #[test]
+    fn test_call_hierarchy_in_macros_outgoing() {
+        check_hierarchy(
+            r#"
+macro_rules! define {
+    ($ident:ident) => {
+        fn $ident {}
+    }
+}
+macro_rules! call {
+    ($ident:ident) => {
+        $ident()
+    }
+}
+define!(callee)
+fn caller$0() {
+    call!(callee);
+}
+"#,
+            expect![[r#"caller Function FileId(0) 160..194 163..169"#]],
+            expect![[]],
+            // FIXME
+            expect![[]],
         );
     }
 }