]> git.lizzy.rs Git - rust.git/commitdiff
# This is a combination of 3 commits.
authorAnatol Ulrich <anatol.ulrich@ferrous-systems.com>
Tue, 14 Sep 2021 02:01:21 +0000 (04:01 +0200)
committerAnatol Ulrich <anatol.ulrich@ferrous-systems.com>
Wed, 15 Sep 2021 16:04:45 +0000 (18:04 +0200)
# This is the 1st commit message:

add multi-token mappings for hover

# The commit message #2 will be skipped:

# make fallback an Option instead of vec

# The commit message #3 will be skipped:

# fix indentation

crates/ide/src/hover.rs

index 68b75f3ffc3f6751c9c66c2866d60e0beefe3004..ae53677f76bedeceea1155e67eeb6426e5ee4b00 100644 (file)
@@ -1,3 +1,5 @@
+use std::{collections::HashSet, ops::ControlFlow};
+
 use either::Either;
 use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
 use ide_db::{
@@ -13,7 +15,7 @@
 use stdx::format_to;
 use syntax::{
     algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
-    SyntaxNode, SyntaxToken, T,
+    SyntaxNode, SyntaxToken, TextRange, TextSize, T,
 };
 
 use crate::{
@@ -111,16 +113,68 @@ pub(crate) fn hover(
         kind if kind.is_trivia() => 0,
         _ => 1,
     })?;
-    let token = sema.descend_into_macros(token);
-    let node = token.parent()?;
+
+    let mut seen = HashSet::default();
+
+    let mut fallback = None;
+    sema.descend_into_macros_many(token.clone())
+        .iter()
+        .filter_map(|token| match token.parent() {
+            Some(node) => {
+                match find_hover_result(&sema, file_id, offset, config, token, &node, &mut seen) {
+                    Some(res) => match res {
+                        ControlFlow::Break(inner) => Some(inner),
+                        ControlFlow::Continue(_) => {
+                            if fallback.is_none() {
+                                // FIXME we're only taking the first fallback into account that's not `None`
+                                fallback = type_hover(&sema, config, &token);
+                            }
+                            None
+                        }
+                    },
+                    None => None,
+                }
+            }
+            None => None,
+        })
+        // reduce all descends into a single `RangeInfo`
+        // that spans from the earliest start to the latest end (fishy/FIXME),
+        // concatenates all `Markup`s with `\n---\n`,
+        // and accumulates all actions into its `actions` vector.
+        .reduce(|mut acc, RangeInfo { range, mut info }| {
+            let start = acc.range.start().min(range.start());
+            let end = acc.range.end().max(range.end());
+
+            acc.range = TextRange::new(start, end);
+            acc.info.actions.append(&mut info.actions);
+            acc.info.markup = Markup::from(format!("{}\n---\n{}", acc.info.markup, info.markup));
+            acc
+        })
+        .or(fallback)
+}
+
+fn find_hover_result(
+    sema: &Semantics<RootDatabase>,
+    file_id: FileId,
+    offset: TextSize,
+    config: &HoverConfig,
+    token: &SyntaxToken,
+    node: &SyntaxNode,
+    seen: &mut HashSet<Definition>,
+) -> Option<ControlFlow<RangeInfo<HoverResult>>> {
     let mut range_override = None;
+
+    // intra-doc links and attributes are special cased
+    // so don't add them to the `seen` duplicate check
+    let mut add_to_seen_definitions = true;
+
     let definition = match_ast! {
         match node {
-            ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
+            ast::Name(name) => NameClass::classify(sema, &name).map(|class| match class {
                 NameClass::Definition(it) | NameClass::ConstReference(it) => it,
                 NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
             }),
-            ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|class| match class {
+            ast::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref).map(|class| match class {
                 NameRefClass::Definition(def) => def,
                 NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
                     Definition::Field(field_ref)
@@ -137,25 +191,37 @@ pub(crate) fn hover(
             ),
             _ => {
                 // intra-doc links
+                // FIXME: move comment + attribute special cases somewhere else to simplify control flow,
+                // hopefully simplifying the return type of this function in the process
+                // (the `Break`/`Continue` distinction is needed to decide whether to use fallback hovers)
+                //
+                // FIXME: hovering the intra doc link to `Foo` not working:
+                //
+                // #[identity]
+                // trait Foo {
+                //    /// [`Foo`]
+                // fn foo() {}
                 if token.kind() == COMMENT {
+                    add_to_seen_definitions = false;
                     cov_mark::hit!(no_highlight_on_comment_hover);
-                    let (attributes, def) = doc_attributes(&sema, &node)?;
-                    let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
+                    let (attributes, def) = doc_attributes(sema, node)?;
+                    let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
                     let (idl_range, link, ns) =
                         extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
                             let mapped = doc_mapping.map(range)?;
                             (mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
                         })?;
                     range_override = Some(idl_range);
-                    Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
+                    Some(match resolve_doc_path_for_def(sema.db,def, &link,ns)? {
                         Either::Left(it) => Definition::ModuleDef(it),
                         Either::Right(it) => Definition::Macro(it),
                     })
                 // attributes, require special machinery as they are mere ident tokens
                 } else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
+                    add_to_seen_definitions = false;
                     // lints
-                    if let res@Some(_) = try_hover_for_lint(&attr, &token) {
-                        return res;
+                    if let Some(res) = try_hover_for_lint(&attr, &token) {
+                        return Some(ControlFlow::Break(res));
                     // derives
                     } else {
                         range_override = Some(token.text_range());
@@ -169,20 +235,29 @@ pub(crate) fn hover(
     };
 
     if let Some(definition) = definition {
+        // skip duplicates
+        if seen.contains(&definition) {
+            return None;
+        }
+        if add_to_seen_definitions {
+            seen.insert(definition);
+        }
         let famous_defs = match &definition {
             Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
                 Some(FamousDefs(&sema, sema.scope(&node).krate()))
             }
             _ => None,
         };
-        if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) {
+        if let Some(markup) =
+            hover_for_definition(sema.db, definition, famous_defs.as_ref(), config)
+        {
             let mut res = HoverResult::default();
             res.markup = process_markup(sema.db, definition, &markup, config);
-            if let Some(action) = show_implementations_action(db, definition) {
+            if let Some(action) = show_implementations_action(sema.db, definition) {
                 res.actions.push(action);
             }
 
-            if let Some(action) = show_fn_references_action(db, definition) {
+            if let Some(action) = show_fn_references_action(sema.db, definition) {
                 res.actions.push(action);
             }
 
@@ -190,21 +265,27 @@ pub(crate) fn hover(
                 res.actions.push(action);
             }
 
-            if let Some(action) = goto_type_action_for_def(db, definition) {
+            if let Some(action) = goto_type_action_for_def(sema.db, definition) {
                 res.actions.push(action);
             }
 
             let range = range_override.unwrap_or_else(|| sema.original_range(&node).range);
-            return Some(RangeInfo::new(range, res));
+            return Some(ControlFlow::Break(RangeInfo::new(range, res)));
         }
     }
 
-    if let res @ Some(_) = hover_for_keyword(&sema, config, &token) {
-        return res;
+    if let Some(res) = hover_for_keyword(&sema, config, &token) {
+        return Some(ControlFlow::Break(res));
     }
 
-    // No definition below cursor, fall back to showing type hovers.
+    Some(ControlFlow::Continue(()))
+}
 
+fn type_hover(
+    sema: &Semantics<RootDatabase>,
+    config: &HoverConfig,
+    token: &SyntaxToken,
+) -> Option<RangeInfo<HoverResult>> {
     let node = token
         .ancestors()
         .take_while(|it| !ast::Item::can_cast(it.kind()))
@@ -214,7 +295,7 @@ pub(crate) fn hover(
         match node {
             ast::Expr(it) => Either::Left(it),
             ast::Pat(it) => Either::Right(it),
-            // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
+            // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
             // (e.g expanding a builtin macro). So we give up here.
             ast::MacroCall(_it) => return None,
             _ => return None,
@@ -223,6 +304,7 @@ pub(crate) fn hover(
 
     let res = hover_type_info(&sema, config, &expr_or_pat)?;
     let range = sema.original_range(&node).range;
+
     Some(RangeInfo::new(range, res))
 }
 
@@ -845,6 +927,82 @@ fn check_hover_range_no_results(ra_fixture: &str) {
         assert!(hover.is_none());
     }
 
+    #[test]
+    fn hover_descend_macros_avoids_duplicates() {
+        check(
+            r#"
+macro_rules! dupe_use {
+    ($local:ident) => {
+        {
+            $local;
+            $local;
+        }
+    }
+}
+fn foo() {
+    let local = 0;
+    dupe_use!(local$0);
+}
+"#,
+            expect![[r#"
+            *local*
+
+            ```rust
+            let local: i32
+            ```
+        "#]],
+        );
+    }
+
+    #[test]
+    fn hover_shows_all_macro_descends() {
+        check(
+            r#"
+macro_rules! m {
+    ($name:ident) => {
+        /// Outer
+        fn $name() {}
+
+        mod module {
+            /// Inner
+            fn $name() {}
+        }
+    };
+}
+
+m!(ab$0c);
+            "#,
+            expect![[r#"
+            *abc*
+
+            ```rust
+            test::module
+            ```
+
+            ```rust
+            fn abc()
+            ```
+
+            ---
+
+            Inner
+            ---
+
+            ```rust
+            test
+            ```
+
+            ```rust
+            fn abc()
+            ```
+
+            ---
+
+            Outer
+            "#]],
+        );
+    }
+
     #[test]
     fn hover_shows_type_of_an_expression() {
         check(