]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/hover.rs
Merge #7851
[rust.git] / crates / ide / src / hover.rs
index 8cb4a51d8e220d818dc02c9fe77a7b2e82b65114..a9454cfa317342c56225449c240640edd2df8259 100644 (file)
@@ -1,10 +1,11 @@
 use hir::{
-    Adt, AsAssocItem, AssocItemContainer, FieldSource, HasAttrs, HasSource, HirDisplay, Module,
-    ModuleDef, ModuleSource, Semantics,
+    Adt, AsAssocItem, AssocItemContainer, FieldSource, GenericParam, HasAttrs, HasSource,
+    HirDisplay, Module, ModuleDef, ModuleSource, Semantics,
 };
-use ide_db::base_db::SourceDatabase;
 use ide_db::{
+    base_db::SourceDatabase,
     defs::{Definition, NameClass, NameRefClass},
+    helpers::FamousDefs,
     RootDatabase,
 };
 use itertools::Itertools;
@@ -17,7 +18,7 @@
     doc_links::{remove_links, rewrite_links},
     markdown_remove::remove_markdown,
     markup::Markup,
-    runnables::{runnable, runnable_fn},
+    runnables::{runnable_fn, runnable_mod},
     FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
 };
 
@@ -94,7 +95,12 @@ pub(crate) fn hover(
     let node = token.parent();
     let definition = match_ast! {
         match node {
-            ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)),
+            // we don't use NameClass::referenced_or_defined here as we do not want to resolve
+            // field pattern shorthands to their definition
+            ast::Name(name) => NameClass::classify(&sema, &name).and_then(|class| match class {
+                NameClass::ConstReference(def) => Some(def),
+                def => def.defined(sema.db),
+            }),
             ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
             ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime)
                 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)),
@@ -102,16 +108,14 @@ pub(crate) fn hover(
         }
     };
     if let Some(definition) = definition {
-        if let Some(markup) = hover_for_definition(db, definition) {
-            let markup = markup.as_str();
-            let markup = if !markdown {
-                remove_markdown(markup)
-            } else if links_in_hover {
-                rewrite_links(db, markup, &definition)
-            } else {
-                remove_links(markup)
-            };
-            res.markup = Markup::from(markup);
+        let famous_defs = match &definition {
+            Definition::ModuleDef(ModuleDef::BuiltinType(_)) => {
+                Some(FamousDefs(&sema, sema.scope(&node).krate()))
+            }
+            _ => None,
+        };
+        if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref()) {
+            res.markup = process_markup(sema.db, definition, &markup, links_in_hover, markdown);
             if let Some(action) = show_implementations_action(db, definition) {
                 res.actions.push(action);
             }
@@ -133,18 +137,18 @@ pub(crate) fn hover(
         // don't highlight the entire parent node on comment hover
         return None;
     }
+    if let res @ Some(_) = hover_for_keyword(&sema, links_in_hover, markdown, &token) {
+        return res;
+    }
 
-    let node = token.ancestors().find(|n| {
-        ast::Expr::can_cast(n.kind())
-            || ast::Pat::can_cast(n.kind())
-            || ast::SelfParam::can_cast(n.kind())
-    })?;
+    let node = token
+        .ancestors()
+        .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
 
     let ty = match_ast! {
         match node {
             ast::Expr(it) => sema.type_of_expr(&it)?,
             ast::Pat(it) => sema.type_of_pat(&it)?,
-            ast::SelfParam(self_param) => sema.type_of_self(&self_param)?,
             // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
             // (e.g expanding a builtin macro). So we give up here.
             ast::MacroCall(_it) => return None,
@@ -175,12 +179,7 @@ fn to_action(nav_target: NavigationTarget) -> HoverAction {
         Definition::SelfType(it) => it.target_ty(db).as_adt(),
         _ => None,
     }?;
-    match adt {
-        Adt::Struct(it) => it.try_to_nav(db),
-        Adt::Union(it) => it.try_to_nav(db),
-        Adt::Enum(it) => it.try_to_nav(db),
-    }
-    .map(to_action)
+    adt.try_to_nav(db).map(to_action)
 }
 
 fn runnable_action(
@@ -190,12 +189,7 @@ fn runnable_action(
 ) -> Option<HoverAction> {
     match def {
         Definition::ModuleDef(it) => match it {
-            ModuleDef::Module(it) => match it.definition_source(sema.db).value {
-                ModuleSource::Module(it) => {
-                    runnable(&sema, it.syntax().clone()).map(|it| HoverAction::Runnable(it))
-                }
-                _ => None,
-            },
+            ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)),
             ModuleDef::Function(func) => {
                 let src = func.source(sema.db)?;
                 if src.file_id != file_id.into() {
@@ -220,12 +214,12 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
         }
     };
 
-    if let Definition::TypeParam(it) = def {
+    if let Definition::GenericParam(GenericParam::TypeParam(it)) = def {
         it.trait_bounds(db).into_iter().for_each(|it| push_new_def(it.into()));
     } else {
         let ty = match def {
             Definition::Local(it) => it.ty(db),
-            Definition::ConstParam(it) => it.ty(db),
+            Definition::GenericParam(GenericParam::ConstParam(it)) => it.ty(db),
             _ => return None,
         };
 
@@ -280,6 +274,24 @@ fn hover_markup(
     }
 }
 
+fn process_markup(
+    db: &RootDatabase,
+    def: Definition,
+    markup: &Markup,
+    links_in_hover: bool,
+    markdown: bool,
+) -> Markup {
+    let markup = markup.as_str();
+    let markup = if !markdown {
+        remove_markdown(markup)
+    } else if links_in_hover {
+        rewrite_links(db, markup, &def)
+    } else {
+        remove_links(markup)
+    };
+    Markup::from(markup)
+}
+
 fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
     match def {
         Definition::Field(f) => Some(f.parent_def(db).name(db)),
@@ -312,7 +324,11 @@ fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
     def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
 }
 
-fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
+fn hover_for_definition(
+    db: &RootDatabase,
+    def: Definition,
+    famous_defs: Option<&FamousDefs>,
+) -> Option<Markup> {
     let mod_path = definition_mod_path(db, &def);
     return match def {
         Definition::Macro(it) => {
@@ -334,6 +350,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
                 match it.definition_source(db).value {
                     ModuleSource::Module(it) => it.short_label(),
                     ModuleSource::SourceFile(it) => it.short_label(),
+                    ModuleSource::BlockExpr(it) => it.short_label(),
                 },
                 mod_path,
             ),
@@ -346,7 +363,9 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
             ModuleDef::Static(it) => from_def_source(db, it, mod_path),
             ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
             ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
-            ModuleDef::BuiltinType(it) => Some(Markup::fenced_block(&it)),
+            ModuleDef::BuiltinType(it) => famous_defs
+                .and_then(|fd| hover_for_builtin(fd, it))
+                .or_else(|| Some(Markup::fenced_block(&it.name()))),
         },
         Definition::Local(it) => Some(Markup::fenced_block(&it.ty(db).display(db))),
         Definition::SelfType(impl_def) => {
@@ -357,9 +376,11 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
             })
         }
         Definition::Label(it) => Some(Markup::fenced_block(&it.name(db))),
-        Definition::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))),
-        Definition::TypeParam(type_param) => Some(Markup::fenced_block(&type_param.display(db))),
-        Definition::ConstParam(it) => from_def_source(db, it, None),
+        Definition::GenericParam(it) => match it {
+            GenericParam::TypeParam(it) => Some(Markup::fenced_block(&it.display(db))),
+            GenericParam::LifetimeParam(it) => Some(Markup::fenced_block(&it.name(db))),
+            GenericParam::ConstParam(it) => from_def_source(db, it, None),
+        },
     };
 
     fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
@@ -385,11 +406,52 @@ fn from_def_source_labeled<D>(
     }
 }
 
+fn hover_for_keyword(
+    sema: &Semantics<RootDatabase>,
+    links_in_hover: bool,
+    markdown: bool,
+    token: &SyntaxToken,
+) -> Option<RangeInfo<HoverResult>> {
+    if !token.kind().is_keyword() {
+        return None;
+    }
+    let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()).krate());
+    // std exposes {}_keyword modules with docstrings on the root to document keywords
+    let keyword_mod = format!("{}_keyword", token.text());
+    let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
+    let docs = doc_owner.attrs(sema.db).docs()?;
+    let markup = process_markup(
+        sema.db,
+        Definition::ModuleDef(doc_owner.into()),
+        &hover_markup(Some(docs.into()), Some(token.text().into()), None)?,
+        links_in_hover,
+        markdown,
+    );
+    Some(RangeInfo::new(token.text_range(), HoverResult { markup, actions: Default::default() }))
+}
+
+fn hover_for_builtin(famous_defs: &FamousDefs, builtin: hir::BuiltinType) -> Option<Markup> {
+    // std exposes prim_{} modules with docstrings on the root to document the builtins
+    let primitive_mod = format!("prim_{}", builtin.name());
+    let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
+    let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
+    hover_markup(Some(docs.into()), Some(builtin.name().to_string()), None)
+}
+
+fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module> {
+    let db = famous_defs.0.db;
+    let std_crate = famous_defs.std()?;
+    let std_root_module = std_crate.root_module(db);
+    std_root_module
+        .children(db)
+        .find(|module| module.name(db).map_or(false, |module| module.to_string() == name))
+}
+
 fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
     return tokens.max_by_key(priority);
     fn priority(n: &SyntaxToken) -> usize {
         match n.kind() {
-            IDENT | INT_NUMBER | LIFETIME_IDENT => 3,
+            IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
             T!['('] | T![')'] => 2,
             kind if kind.is_trivia() => 0,
             _ => 1,
@@ -1836,6 +1898,35 @@ pub struct Bar
             "#]],
         );
     }
+    #[test]
+    fn test_hover_intra_link_reference_to_trait_method() {
+        check(
+            r#"
+pub trait Foo {
+    fn buzz() -> usize;
+}
+/// [Foo][buzz]
+///
+/// [buzz]: Foo::buzz
+pub struct B$0ar
+"#,
+            expect![[r#"
+                *Bar*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                pub struct Bar
+                ```
+
+                ---
+
+                [Foo](https://docs.rs/test/*/test/trait.Foo.html#tymethod.buzz)
+            "#]],
+        );
+    }
 
     #[test]
     fn test_hover_external_url() {
@@ -1951,16 +2042,16 @@ fn test_hover_no_links() {
 /// Test cases:
 /// case 1.  bare URL: https://www.example.com/
 /// case 2.  inline URL with title: [example](https://www.example.com/)
-/// case 3.  code refrence: [`Result`]
-/// case 4.  code refrence but miss footnote: [`String`]
+/// case 3.  code reference: [`Result`]
+/// case 4.  code reference but miss footnote: [`String`]
 /// case 5.  autolink: <http://www.example.com/>
 /// case 6.  email address: <test@example.com>
-/// case 7.  refrence: [example][example]
+/// case 7.  reference: [example][example]
 /// case 8.  collapsed link: [example][]
 /// case 9.  shortcut link: [example]
 /// case 10. inline without URL: [example]()
-/// case 11. refrence: [foo][foo]
-/// case 12. refrence: [foo][bar]
+/// case 11. reference: [foo][foo]
+/// case 12. reference: [foo][bar]
 /// case 13. collapsed link: [foo][]
 /// case 14. shortcut link: [foo]
 /// case 15. inline without URL: [foo]()
@@ -1987,16 +2078,16 @@ pub fn foo()
                 Test cases:
                 case 1.  bare URL: https://www.example.com/
                 case 2.  inline URL with title: [example](https://www.example.com/)
-                case 3.  code refrence: `Result`
-                case 4.  code refrence but miss footnote: `String`
+                case 3.  code reference: `Result`
+                case 4.  code reference but miss footnote: `String`
                 case 5.  autolink: http://www.example.com/
                 case 6.  email address: test@example.com
-                case 7.  refrence: example
+                case 7.  reference: example
                 case 8.  collapsed link: example
                 case 9.  shortcut link: example
                 case 10. inline without URL: example
-                case 11. refrence: foo
-                case 12. refrence: foo
+                case 11. reference: foo
+                case 12. reference: foo
                 case 13. collapsed link: foo
                 case 14. shortcut link: foo
                 case 15. inline without URL: foo
@@ -3132,6 +3223,39 @@ fn foo<T: Foo>(t: T$0){}
         );
     }
 
+    #[test]
+    fn test_hover_self_has_go_to_type() {
+        check_actions(
+            r#"
+struct Foo;
+impl Foo {
+    fn foo(&self$0) {}
+}
+"#,
+            expect![[r#"
+                [
+                    GoToType(
+                        [
+                            HoverGotoTypeData {
+                                mod_path: "test::Foo",
+                                nav: NavigationTarget {
+                                    file_id: FileId(
+                                        0,
+                                    ),
+                                    full_range: 0..11,
+                                    focus_range: 7..10,
+                                    name: "Foo",
+                                    kind: Struct,
+                                    description: "struct Foo",
+                                },
+                            },
+                        ],
+                    ),
+                ]
+            "#]],
+        );
+    }
+
     #[test]
     fn hover_displays_normalized_crate_names() {
         check(
@@ -3195,7 +3319,8 @@ fn bar(&sel$0f) {}
 }
 "#,
             expect![[r#"
-                *&self*
+                *self*
+
                 ```rust
                 &Foo
                 ```
@@ -3214,7 +3339,8 @@ fn bar(sel$0f: Arc<Foo>) {}
 }
 "#,
             expect![[r#"
-                *self: Arc<Foo>*
+                *self*
+
                 ```rust
                 Arc<Foo>
                 ```
@@ -3358,7 +3484,7 @@ impl<T> Foo<T$0> {}
                 ```
                 "#]],
         );
-        // lifetimes aren't being substituted yet
+        // lifetimes bounds arent being tracked yet
         check(
             r#"
 struct Foo<T>(T);
@@ -3368,7 +3494,7 @@ impl<T: 'static> Foo<T$0> {}
                 *T*
 
                 ```rust
-                T: {error}
+                T
                 ```
                 "#]],
         );
@@ -3390,4 +3516,122 @@ impl<const LEN: usize> Foo<LEN$0> {}
             "#]],
         );
     }
+
+    #[test]
+    fn hover_const_pat() {
+        check(
+            r#"
+/// This is a doc
+const FOO: usize = 3;
+fn foo() {
+    match 5 {
+        FOO$0 => (),
+        _ => ()
+    }
+}
+"#,
+            expect![[r#"
+                *FOO*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                const FOO: usize = 3
+                ```
+
+                ---
+
+                This is a doc
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_mod_def() {
+        check(
+            r#"
+//- /main.rs
+mod foo$0;
+//- /foo.rs
+//! For the horde!
+"#,
+            expect![[r#"
+                *foo*
+                For the horde!
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_self_in_use() {
+        check(
+            r#"
+//! This should not appear
+mod foo {
+    /// But this should appear
+    pub mod bar {}
+}
+use foo::bar::{self$0};
+"#,
+            expect![[r#"
+                *self*
+
+                ```rust
+                test::foo
+                ```
+
+                ```rust
+                pub mod bar
+                ```
+
+                ---
+
+                But this should appear
+            "#]],
+        )
+    }
+
+    #[test]
+    fn hover_keyword() {
+        let ra_fixture = r#"//- /main.rs crate:main deps:std
+fn f() { retur$0n; }"#;
+        let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
+        check(
+            &fixture,
+            expect![[r#"
+                *return*
+
+                ```rust
+                return
+                ```
+
+                ---
+
+                Docs for return_keyword
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_builtin() {
+        let ra_fixture = r#"//- /main.rs crate:main deps:std
+cosnt _: &str$0 = ""; }"#;
+        let fixture = format!("{}\n{}", ra_fixture, FamousDefs::FIXTURE);
+        check(
+            &fixture,
+            expect![[r#"
+                *str*
+
+                ```rust
+                str
+                ```
+
+                ---
+
+                Docs for prim_str
+            "#]],
+        );
+    }
 }