]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/hover.rs
move function to defs.rs
[rust.git] / crates / ide / src / hover.rs
index afa67f72bed6b8d85610b0ea87aea2d758615b59..80e91f303cd1358019312731e16fd973a9702042 100644 (file)
@@ -1,19 +1,21 @@
+use std::{collections::HashSet, ops::ControlFlow};
+
 use either::Either;
-use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
+use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
 use ide_db::{
-    base_db::SourceDatabase,
-    defs::{Definition, NameClass, NameRefClass},
+    base_db::{FileRange, SourceDatabase},
+    defs::Definition,
     helpers::{
         generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
-        pick_best_token, try_resolve_derive_input_at, FamousDefs,
+        pick_best_token, FamousDefs,
     },
     RootDatabase,
 };
 use itertools::Itertools;
 use stdx::format_to;
 use syntax::{
-    algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
-    SyntaxKind::*, SyntaxToken, T,
+    algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
+    SyntaxNode, SyntaxToken, TextRange, TextSize, T,
 };
 
 use crate::{
@@ -54,6 +56,25 @@ pub enum HoverAction {
     GoToType(Vec<HoverGotoTypeData>),
 }
 
+impl HoverAction {
+    fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Self {
+        let targets = targets
+            .into_iter()
+            .filter_map(|it| {
+                Some(HoverGotoTypeData {
+                    mod_path: render_path(
+                        db,
+                        it.module(db)?,
+                        it.name(db).map(|name| name.to_string()),
+                    ),
+                    nav: it.try_to_nav(db)?,
+                })
+            })
+            .collect();
+        HoverAction::GoToType(targets)
+    }
+}
+
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub struct HoverGotoTypeData {
     pub mod_path: String,
@@ -69,134 +90,424 @@ pub struct HoverResult {
 
 // Feature: Hover
 //
-// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
+// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
 // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
 //
 // image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
 pub(crate) fn hover(
     db: &RootDatabase,
-    position: FilePosition,
+    FileRange { file_id, range }: FileRange,
     config: &HoverConfig,
 ) -> Option<RangeInfo<HoverResult>> {
     let sema = hir::Semantics::new(db);
-    let file = sema.parse(position.file_id).syntax().clone();
-    let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
+    let file = sema.parse(file_id).syntax().clone();
+
+    if !range.is_empty() {
+        return hover_ranged(&file, range, &sema, config);
+    }
+    let offset = range.start();
+
+    let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
         IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
         T!['('] | T![')'] => 2,
         kind if kind.is_trivia() => 0,
         _ => 1,
     })?;
-    let token = sema.descend_into_macros(token);
 
-    let mut res = HoverResult::default();
+    let mut seen = HashSet::default();
 
-    let node = token.parent()?;
-    let mut range = None;
-    let definition = match_ast! {
-        match node {
-            // 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).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 {
-                NameRefClass::Definition(def) => def,
-                NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
-                    Definition::Field(field_ref)
-                }
-            }),
-            ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else(
-                || NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
-                    NameRefClass::Definition(it) => Some(it),
-                    _ => None,
-                }),
-                |d| d.defined(),
-            ),
-            _ => {
-                if ast::Comment::cast(token.clone()).is_some() {
-                    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 (idl_range, link, ns) =
-                        extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
-                            let hir::InFile { file_id, value: mapped_range } = doc_mapping.map(range)?;
-                            (file_id == position.file_id.into() && mapped_range.contains(position.offset)).then(||(mapped_range, link, ns))
-                        })?;
-                    range = Some(idl_range);
-                    resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
-                } else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
-                    if let res@Some(_) = try_hover_for_lint(&attr, &token) {
-                        return res;
-                    } else {
-                        try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
-                    }
-                } else {
-                    None
-                }
-            },
-        }
-    };
+    let mut fallback = None;
+    // attributes, require special machinery as they are mere ident tokens
 
-    if let Some(definition) = 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) {
-            res.markup = process_markup(sema.db, definition, &markup, config);
-            if let Some(action) = show_implementations_action(db, definition) {
-                res.actions.push(action);
-            }
+    let descend_macros = sema.descend_into_macros_many(original_token.clone());
 
-            if let Some(action) = show_fn_references_action(db, definition) {
-                res.actions.push(action);
+    for token in &descend_macros {
+        if token.kind() != COMMENT {
+            if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
+                // lints
+                if let Some(res) = try_hover_for_lint(&attr, &token) {
+                    return Some(res);
+                }
             }
+        }
+    }
 
-            if let Some(action) = runnable_action(&sema, definition, position.file_id) {
-                res.actions.push(action);
+    descend_macros
+        .iter()
+        .filter_map(|token| match token.parent() {
+            Some(node) => {
+                match find_hover_result(
+                    &sema,
+                    file_id,
+                    offset,
+                    config,
+                    &original_token,
+                    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 = hover_for_keyword(&sema, config, &token)
+                                    .or(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)
+}
 
-            if let Some(action) = goto_type_action(db, definition) {
-                res.actions.push(action);
-            }
+fn find_hover_result(
+    sema: &Semantics<RootDatabase>,
+    file_id: FileId,
+    offset: TextSize,
+    config: &HoverConfig,
+    original_token: &SyntaxToken,
+    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 = Definition::from_node(sema, token).into_iter().next().or_else(|| {
+        // 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(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(sema.db, def, &link, ns)? {
+                Either::Left(it) => Definition::ModuleDef(it),
+                Either::Right(it) => Definition::Macro(it),
+            })
+        } else {
+            None
+        }
+    });
 
-            let range = range.unwrap_or_else(|| sema.original_range(&node).range);
-            return Some(RangeInfo::new(range, res));
+    if let Some(definition) = definition {
+        // skip duplicates
+        if seen.contains(&definition) {
+            return None;
+        }
+        if add_to_seen_definitions {
+            seen.insert(definition);
+        }
+        if let Some(res) = hover_for_definition(sema, file_id, definition, &node, config) {
+            let range = range_override.unwrap_or_else(|| original_token.text_range());
+            return Some(ControlFlow::Break(RangeInfo::new(range, res)));
         }
     }
 
-    if let res @ Some(_) = hover_for_keyword(&sema, config, &token) {
-        return res;
-    }
+    Some(ControlFlow::Continue(()))
+}
 
+fn type_hover(
+    sema: &Semantics<RootDatabase>,
+    config: &HoverConfig,
+    token: &SyntaxToken,
+) -> Option<RangeInfo<HoverResult>> {
+    if token.kind() == COMMENT {
+        return None;
+    }
     let node = token
         .ancestors()
         .take_while(|it| !ast::Item::can_cast(it.kind()))
         .find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
 
-    let ty = match_ast! {
+    let expr_or_pat = match_ast! {
         match node {
-            ast::Expr(it) => sema.type_of_expr(&it)?,
-            ast::Pat(it) => sema.type_of_pat(&it)?,
-            // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
+            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_many` failed to resolve.
             // (e.g expanding a builtin macro). So we give up here.
             ast::MacroCall(_it) => return None,
             _ => return None,
         }
     };
 
-    res.markup = if config.markdown() {
-        Markup::fenced_block(&ty.display(db))
-    } else {
-        ty.display(db).to_string().into()
-    };
+    let res = hover_type_info(&sema, config, &expr_or_pat)?;
     let range = sema.original_range(&node).range;
     Some(RangeInfo::new(range, res))
 }
 
+fn hover_ranged(
+    file: &SyntaxNode,
+    range: syntax::TextRange,
+    sema: &Semantics<RootDatabase>,
+    config: &HoverConfig,
+) -> Option<RangeInfo<HoverResult>> {
+    let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
+        match_ast! {
+            match it {
+                ast::Expr(expr) => Some(Either::Left(expr)),
+                ast::Pat(pat) => Some(Either::Right(pat)),
+                _ => None,
+            }
+        }
+    })?;
+    let res = match &expr_or_pat {
+        Either::Left(ast::Expr::TryExpr(try_expr)) => hover_try_expr(sema, config, try_expr),
+        Either::Left(ast::Expr::PrefixExpr(prefix_expr))
+            if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
+        {
+            hover_deref_expr(sema, config, prefix_expr)
+        }
+        _ => None,
+    };
+    let res = res.or_else(|| hover_type_info(sema, config, &expr_or_pat));
+    res.map(|it| {
+        let range = match expr_or_pat {
+            Either::Left(it) => it.syntax().text_range(),
+            Either::Right(it) => it.syntax().text_range(),
+        };
+        RangeInfo::new(range, it)
+    })
+}
+
+fn hover_try_expr(
+    sema: &Semantics<RootDatabase>,
+    config: &HoverConfig,
+    try_expr: &ast::TryExpr,
+) -> Option<HoverResult> {
+    let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
+    let mut ancestors = try_expr.syntax().ancestors();
+    let mut body_ty = loop {
+        let next = ancestors.next()?;
+        break match_ast! {
+            match next {
+                ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
+                ast::Item(__) => return None,
+                ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
+                ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
+                    sema.type_of_expr(&effect.block_expr()?.into())?.original
+                } else {
+                    continue;
+                },
+                _ => continue,
+            }
+        };
+    };
+
+    if inner_ty == body_ty {
+        return None;
+    }
+
+    let mut inner_ty = inner_ty;
+    let mut s = "Try Target".to_owned();
+
+    let adts = inner_ty.as_adt().zip(body_ty.as_adt());
+    if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
+        let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate());
+        // special case for two options, there is no value in showing them
+        if let Some(option_enum) = famous_defs.core_option_Option() {
+            if inner == option_enum && body == option_enum {
+                cov_mark::hit!(hover_try_expr_opt_opt);
+                return None;
+            }
+        }
+
+        // special case two results to show the error variants only
+        if let Some(result_enum) = famous_defs.core_result_Result() {
+            if inner == result_enum && body == result_enum {
+                let error_type_args =
+                    inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
+                if let Some((inner, body)) = error_type_args {
+                    inner_ty = inner;
+                    body_ty = body;
+                    s = "Try Error".to_owned();
+                }
+            }
+        }
+    }
+
+    let mut res = HoverResult::default();
+
+    let mut targets: Vec<hir::ModuleDef> = Vec::new();
+    let mut push_new_def = |item: hir::ModuleDef| {
+        if !targets.contains(&item) {
+            targets.push(item);
+        }
+    };
+    walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+    walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
+    res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+    let inner_ty = inner_ty.display(sema.db).to_string();
+    let body_ty = body_ty.display(sema.db).to_string();
+    let ty_len_max = inner_ty.len().max(body_ty.len());
+
+    let l = "Propagated as: ".len() - " Type: ".len();
+    let static_text_len_diff = l as isize - s.len() as isize;
+    let tpad = static_text_len_diff.max(0) as usize;
+    let ppad = static_text_len_diff.min(0).abs() as usize;
+
+    res.markup = format!(
+        "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+        s,
+        inner_ty,
+        body_ty,
+        pad0 = ty_len_max + tpad,
+        pad1 = ty_len_max + ppad,
+        bt_start = if config.markdown() { "```text\n" } else { "" },
+        bt_end = if config.markdown() { "```\n" } else { "" }
+    )
+    .into();
+    Some(res)
+}
+
+fn hover_deref_expr(
+    sema: &Semantics<RootDatabase>,
+    config: &HoverConfig,
+    deref_expr: &ast::PrefixExpr,
+) -> Option<HoverResult> {
+    let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
+    let TypeInfo { original, adjusted } =
+        sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
+
+    let mut res = HoverResult::default();
+    let mut targets: Vec<hir::ModuleDef> = Vec::new();
+    let mut push_new_def = |item: hir::ModuleDef| {
+        if !targets.contains(&item) {
+            targets.push(item);
+        }
+    };
+    walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
+    walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+    res.markup = if let Some(adjusted_ty) = adjusted {
+        walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+        let original = original.display(sema.db).to_string();
+        let adjusted = adjusted_ty.display(sema.db).to_string();
+        let inner = inner_ty.display(sema.db).to_string();
+        let type_len = "To type: ".len();
+        let coerced_len = "Coerced to: ".len();
+        let deref_len = "Dereferenced from: ".len();
+        let max_len = (original.len() + type_len)
+            .max(adjusted.len() + coerced_len)
+            .max(inner.len() + deref_len);
+        format!(
+            "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+            inner,
+            original,
+            adjusted,
+            ipad = max_len - deref_len,
+            apad = max_len - type_len,
+            opad = max_len - coerced_len,
+            bt_start = if config.markdown() { "```text\n" } else { "" },
+            bt_end = if config.markdown() { "```\n" } else { "" }
+        )
+        .into()
+    } else {
+        let original = original.display(sema.db).to_string();
+        let inner = inner_ty.display(sema.db).to_string();
+        let type_len = "To type: ".len();
+        let deref_len = "Dereferenced from: ".len();
+        let max_len = (original.len() + type_len).max(inner.len() + deref_len);
+        format!(
+            "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+            inner,
+            original,
+            ipad = max_len - deref_len,
+            apad = max_len - type_len,
+            bt_start = if config.markdown() { "```text\n" } else { "" },
+            bt_end = if config.markdown() { "```\n" } else { "" }
+        )
+        .into()
+    };
+    res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+
+    Some(res)
+}
+
+fn hover_type_info(
+    sema: &Semantics<RootDatabase>,
+    config: &HoverConfig,
+    expr_or_pat: &Either<ast::Expr, ast::Pat>,
+) -> Option<HoverResult> {
+    let TypeInfo { original, adjusted } = match expr_or_pat {
+        Either::Left(expr) => sema.type_of_expr(expr)?,
+        Either::Right(pat) => sema.type_of_pat(pat)?,
+    };
+
+    let mut res = HoverResult::default();
+    let mut targets: Vec<hir::ModuleDef> = Vec::new();
+    let mut push_new_def = |item: hir::ModuleDef| {
+        if !targets.contains(&item) {
+            targets.push(item);
+        }
+    };
+    walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+    res.markup = if let Some(adjusted_ty) = adjusted {
+        walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+        let original = original.display(sema.db).to_string();
+        let adjusted = adjusted_ty.display(sema.db).to_string();
+        let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
+        format!(
+            "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+            original,
+            adjusted,
+            apad = static_text_diff_len + adjusted.len().max(original.len()),
+            opad = original.len(),
+            bt_start = if config.markdown() { "```text\n" } else { "" },
+            bt_end = if config.markdown() { "```\n" } else { "" }
+        )
+        .into()
+    } else {
+        if config.markdown() {
+            Markup::fenced_block(&original.display(sema.db))
+        } else {
+            original.display(sema.db).to_string().into()
+        }
+    };
+    res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+    Some(res)
+}
+
 fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
     let (path, tt) = attr.as_simple_call()?;
     if !tt.syntax().text_range().contains(token.text_range().start()) {
@@ -241,13 +552,6 @@ fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo
     ))
 }
 
-pub(crate) fn hover_range(
-    db: &RootDatabase,
-    range: FileRange,
-    config: &HoverConfig,
-) -> Option<RangeInfo<HoverResult>> {
-}
-
 fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
     fn to_action(nav_target: NavigationTarget) -> HoverAction {
         HoverAction::Implementation(FilePosition {
@@ -305,7 +609,7 @@ fn runnable_action(
     }
 }
 
-fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
+fn goto_type_action_for_def(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
     let mut targets: Vec<hir::ModuleDef> = Vec::new();
     let mut push_new_def = |item: hir::ModuleDef| {
         if !targets.contains(&item) {
@@ -323,30 +627,28 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
             _ => return None,
         };
 
-        ty.walk(db, |t| {
-            if let Some(adt) = t.as_adt() {
-                push_new_def(adt.into());
-            } else if let Some(trait_) = t.as_dyn_trait() {
-                push_new_def(trait_.into());
-            } else if let Some(traits) = t.as_impl_traits(db) {
-                traits.into_iter().for_each(|it| push_new_def(it.into()));
-            } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
-                push_new_def(trait_.into());
-            }
-        });
+        walk_and_push_ty(db, &ty, &mut push_new_def);
     }
 
-    let targets = targets
-        .into_iter()
-        .filter_map(|it| {
-            Some(HoverGotoTypeData {
-                mod_path: render_path(db, it.module(db)?, it.name(db).map(|name| name.to_string())),
-                nav: it.try_to_nav(db)?,
-            })
-        })
-        .collect();
+    Some(HoverAction::goto_type_from_targets(db, targets))
+}
 
-    Some(HoverAction::GoToType(targets))
+fn walk_and_push_ty(
+    db: &RootDatabase,
+    ty: &hir::Type,
+    push_new_def: &mut dyn FnMut(hir::ModuleDef),
+) {
+    ty.walk(db, |t| {
+        if let Some(adt) = t.as_adt() {
+            push_new_def(adt.into());
+        } else if let Some(trait_) = t.as_dyn_trait() {
+            push_new_def(trait_.into());
+        } else if let Some(traits) = t.as_impl_traits(db) {
+            traits.into_iter().for_each(|it| push_new_def(it.into()));
+        } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
+            push_new_def(trait_.into());
+        }
+    });
 }
 
 fn hover_markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
@@ -417,7 +719,43 @@ 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(
+pub(crate) fn hover_for_definition(
+    sema: &Semantics<RootDatabase>,
+    file_id: FileId,
+    definition: Definition,
+    node: &SyntaxNode,
+    config: &HoverConfig,
+) -> Option<HoverResult> {
+    let famous_defs = match &definition {
+        Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
+            Some(FamousDefs(&sema, sema.scope(&node).krate()))
+        }
+        _ => None,
+    };
+    if let Some(markup) = markup_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(sema.db, definition) {
+            res.actions.push(action);
+        }
+
+        if let Some(action) = show_fn_references_action(sema.db, definition) {
+            res.actions.push(action);
+        }
+
+        if let Some(action) = runnable_action(&sema, definition, file_id) {
+            res.actions.push(action);
+        }
+
+        if let Some(action) = goto_type_action_for_def(sema.db, definition) {
+            res.actions.push(action);
+        }
+        return Some(res);
+    }
+    None
+}
+
+fn markup_for_definition(
     db: &RootDatabase,
     def: Definition,
     famous_defs: Option<&FamousDefs>,
@@ -537,7 +875,8 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module>
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
-    use ide_db::base_db::FileLoader;
+    use ide_db::base_db::{FileLoader, FileRange};
+    use syntax::TextRange;
 
     use crate::{fixture, hover::HoverDocFormat, HoverConfig};
 
@@ -549,10 +888,10 @@ fn check_hover_no_result(ra_fixture: &str) {
                     links_in_hover: true,
                     documentation: Some(HoverDocFormat::Markdown),
                 },
-                position,
+                FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
             )
             .unwrap();
-        assert!(hover.is_none());
+        assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
     }
 
     fn check(ra_fixture: &str, expect: Expect) {
@@ -563,7 +902,7 @@ fn check(ra_fixture: &str, expect: Expect) {
                     links_in_hover: true,
                     documentation: Some(HoverDocFormat::Markdown),
                 },
-                position,
+                FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
             )
             .unwrap()
             .unwrap();
@@ -583,7 +922,7 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
                     links_in_hover: false,
                     documentation: Some(HoverDocFormat::Markdown),
                 },
-                position,
+                FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
             )
             .unwrap()
             .unwrap();
@@ -603,7 +942,7 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
                     links_in_hover: true,
                     documentation: Some(HoverDocFormat::PlainText),
                 },
-                position,
+                FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
             )
             .unwrap()
             .unwrap();
@@ -616,20 +955,125 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
     }
 
     fn check_actions(ra_fixture: &str, expect: Expect) {
-        let (analysis, position) = fixture::position(ra_fixture);
+        let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
         let hover = analysis
             .hover(
                 &HoverConfig {
                     links_in_hover: true,
                     documentation: Some(HoverDocFormat::Markdown),
                 },
-                position,
+                FileRange { file_id, range: position.range_or_empty() },
             )
             .unwrap()
             .unwrap();
         expect.assert_debug_eq(&hover.info.actions)
     }
 
+    fn check_hover_range(ra_fixture: &str, expect: Expect) {
+        let (analysis, range) = fixture::range(ra_fixture);
+        let hover = analysis
+            .hover(
+                &HoverConfig {
+                    links_in_hover: false,
+                    documentation: Some(HoverDocFormat::Markdown),
+                },
+                range,
+            )
+            .unwrap()
+            .unwrap();
+        expect.assert_eq(hover.info.markup.as_str())
+    }
+
+    fn check_hover_range_no_results(ra_fixture: &str) {
+        let (analysis, range) = fixture::range(ra_fixture);
+        let hover = analysis
+            .hover(
+                &HoverConfig {
+                    links_in_hover: false,
+                    documentation: Some(HoverDocFormat::Markdown),
+                },
+                range,
+            )
+            .unwrap();
+        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(
@@ -747,6 +1191,50 @@ pub fn foo(a: u32, b: u32) {}
                 ```
             "#]],
         );
+
+        // Use literal `crate` in path
+        check(
+            r#"
+pub struct X;
+
+fn foo() -> crate::X { X }
+
+fn main() { f$0oo(); }
+        "#,
+            expect![[r#"
+            *foo*
+
+            ```rust
+            test
+            ```
+
+            ```rust
+            fn foo() -> crate::X
+            ```
+        "#]],
+        );
+
+        // Check `super` in path
+        check(
+            r#"
+pub struct X;
+
+mod m { pub fn foo() -> super::X { super::X } }
+
+fn main() { m::f$0oo(); }
+        "#,
+            expect![[r#"
+                *foo*
+
+                ```rust
+                test::m
+                ```
+
+                ```rust
+                pub fn foo() -> super::X
+                ```
+            "#]],
+        );
     }
 
     #[test]
@@ -1110,7 +1598,9 @@ fn hover_for_param_edge() {
     #[test]
     fn hover_for_param_with_multiple_traits() {
         check(
-            r#"trait Deref {
+            r#"
+            //- minicore: sized
+            trait Deref {
                 type Target: ?Sized;
             }
             trait DerefMut {
@@ -1138,7 +1628,7 @@ fn new() -> Thing { Thing { x: 0 } }
 }
 
 fn main() { let foo_$0test = Thing::new(); }
-            "#,
+"#,
             expect![[r#"
                 *foo_test*
 
@@ -1400,6 +1890,28 @@ fn foo()
         );
     }
 
+    #[test]
+    fn test_hover_through_attr() {
+        check(
+            r#"
+//- proc_macros: identity
+#[proc_macros::identity]
+fn foo$0() {}
+"#,
+            expect![[r#"
+                *foo*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                fn foo()
+                ```
+            "#]],
+        );
+    }
+
     #[test]
     fn test_hover_through_expr_in_macro() {
         check(
@@ -1508,7 +2020,7 @@ macro_rules! format {}
             fn foo() {
                 format!("hel$0lo {}", 0);
             }
-            "#,
+"#,
         );
     }
 
@@ -1616,7 +2128,7 @@ fn test_hover_extern_crate() {
 //!
 //! Printed?
 //! abc123
-            "#,
+"#,
             expect![[r#"
                 *std*
 
@@ -1641,7 +2153,7 @@ fn test_hover_extern_crate() {
 //!
 //! Printed?
 //! abc123
-            "#,
+"#,
             expect![[r#"
                 *abc*
 
@@ -2160,7 +2672,7 @@ fn test_hover_struct_has_goto_type_action() {
 struct S{ f1: u32 }
 
 fn main() { let s$0t = S{ f1:0 }; }
-            "#,
+"#,
             expect![[r#"
                 [
                     GoToType(
@@ -2239,7 +2751,7 @@ fn test_hover_generic_struct_has_flattened_goto_type_actions() {
 struct S<T>{ f1: T }
 
 fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
-            "#,
+"#,
             expect![[r#"
                 [
                     GoToType(
@@ -2428,7 +2940,7 @@ trait Bar {}
 fn foo() -> impl Foo + Bar {}
 
 fn main() { let s$0t = foo(); }
-            "#,
+"#,
             expect![[r#"
                 [
                     GoToType(
@@ -2653,8 +3165,8 @@ fn foo() {
                                     file_id: FileId(
                                         1,
                                     ),
-                                    full_range: 251..433,
-                                    focus_range: 290..296,
+                                    full_range: 254..436,
+                                    focus_range: 293..299,
                                     name: "Future",
                                     kind: Trait,
                                     description: "pub trait Future",
@@ -2861,7 +3373,7 @@ struct B<T> {}
 struct S {}
 
 fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
-            "#,
+"#,
             expect![[r#"
                 [
                     GoToType(
@@ -3301,22 +3813,23 @@ fn hover_lifetime() {
     fn hover_type_param() {
         check(
             r#"
+//- minicore: sized
 struct Foo<T>(T);
-trait Copy {}
-trait Clone {}
-trait Sized {}
-impl<T: Copy + Clone> Foo<T$0> where T: Sized {}
+trait TraitA {}
+trait TraitB {}
+impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
 "#,
             expect![[r#"
                 *T*
 
                 ```rust
-                T: Copy + Clone + Sized
+                T: TraitA + TraitB
                 ```
             "#]],
         );
         check(
             r#"
+//- minicore: sized
 struct Foo<T>(T);
 impl<T> Foo<T$0> {}
 "#,
@@ -3331,6 +3844,7 @@ impl<T> Foo<T$0> {}
         // lifetimes bounds arent being tracked yet
         check(
             r#"
+//- minicore: sized
 struct Foo<T>(T);
 impl<T: 'static> Foo<T$0> {}
 "#,
@@ -3344,6 +3858,181 @@ impl<T: 'static> Foo<T$0> {}
         );
     }
 
+    #[test]
+    fn hover_type_param_sized_bounds() {
+        // implicit `: Sized` bound
+        check(
+            r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait> Foo<T$0> {}
+"#,
+            expect![[r#"
+                *T*
+
+                ```rust
+                T: Trait
+                ```
+            "#]],
+        );
+        check(
+            r#"
+//- minicore: sized
+trait Trait {}
+struct Foo<T>(T);
+impl<T: Trait + ?Sized> Foo<T$0> {}
+"#,
+            expect![[r#"
+                *T*
+
+                ```rust
+                T: Trait + ?Sized
+                ```
+            "#]],
+        );
+    }
+
+    mod type_param_sized_bounds {
+        use super::*;
+
+        #[test]
+        fn single_implicit() {
+            check(
+                r#"
+//- minicore: sized
+fn foo<T$0>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T
+                    ```
+                "#]],
+            );
+        }
+
+        #[test]
+        fn single_explicit() {
+            check(
+                r#"
+//- minicore: sized
+fn foo<T$0: Sized>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T
+                    ```
+                "#]],
+            );
+        }
+
+        #[test]
+        fn single_relaxed() {
+            check(
+                r#"
+//- minicore: sized
+fn foo<T$0: ?Sized>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T: ?Sized
+                    ```
+                "#]],
+            );
+        }
+
+        #[test]
+        fn multiple_implicit() {
+            check(
+                r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T: Trait
+                    ```
+                "#]],
+            );
+        }
+
+        #[test]
+        fn multiple_explicit() {
+            check(
+                r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + Sized>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T: Trait
+                    ```
+                "#]],
+            );
+        }
+
+        #[test]
+        fn multiple_relaxed() {
+            check(
+                r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Trait + ?Sized>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T: Trait + ?Sized
+                    ```
+                "#]],
+            );
+        }
+
+        #[test]
+        fn mixed() {
+            check(
+                r#"
+//- minicore: sized
+fn foo<T$0: ?Sized + Sized + Sized>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T
+                    ```
+                "#]],
+            );
+            check(
+                r#"
+//- minicore: sized
+trait Trait {}
+fn foo<T$0: Sized + ?Sized + Sized + Trait>() {}
+"#,
+                expect![[r#"
+                    *T*
+
+                    ```rust
+                    T: Trait
+                    ```
+                "#]],
+            );
+        }
+    }
+
     #[test]
     fn hover_const_param() {
         check(
@@ -3657,7 +4346,7 @@ mod string {
     /// This is `alloc::String`.
     pub struct String;
 }
-            "#,
+"#,
             expect![[r#"
                 *String*
 
@@ -3776,7 +4465,7 @@ fn hover_attr_path_qualifier() {
 //- /lib.rs crate:main.rs deps:foo
 #[fo$0o::bar()]
 struct Foo;
-            "#,
+"#,
             expect![[r#"
                 *foo*
 
@@ -3792,7 +4481,7 @@ fn hover_rename() {
         check(
             r#"
 use self as foo$0;
-            "#,
+"#,
             expect![[r#"
                 *foo*
 
@@ -3805,7 +4494,7 @@ fn hover_rename() {
             r#"
 mod bar {}
 use bar::{self as foo$0};
-            "#,
+"#,
             expect![[r#"
                 *foo*
 
@@ -3823,7 +4512,7 @@ mod bar
 mod bar {
     use super as foo$0;
 }
-            "#,
+"#,
             expect![[r#"
                 *foo*
 
@@ -3835,7 +4524,7 @@ mod bar {
         check(
             r#"
 use crate as foo$0;
-            "#,
+"#,
             expect![[r#"
                 *foo*
 
@@ -3846,6 +4535,36 @@ mod bar {
         );
     }
 
+    #[test]
+    fn hover_attribute_in_macro() {
+        check(
+            r#"
+macro_rules! identity {
+    ($struct:item) => {
+        $struct
+    };
+}
+#[rustc_builtin_macro]
+pub macro Copy {}
+identity!{
+    #[derive(Copy$0)]
+    struct Foo;
+}
+"#,
+            expect![[r#"
+                *Copy*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                pub macro Copy
+                ```
+            "#]],
+        );
+    }
+
     #[test]
     fn hover_derive_input() {
         check(
@@ -3854,9 +4573,9 @@ fn hover_derive_input() {
 pub macro Copy {}
 #[derive(Copy$0)]
 struct Foo;
-            "#,
+"#,
             expect![[r#"
-                *(Copy)*
+                *Copy*
 
                 ```rust
                 test
@@ -3875,9 +4594,9 @@ mod foo {
 }
 #[derive(foo::Copy$0)]
 struct Foo;
-            "#,
+"#,
             expect![[r#"
-                *(foo::Copy)*
+                *Copy*
 
                 ```rust
                 test
@@ -3889,4 +4608,391 @@ mod foo {
             "#]],
         );
     }
+
+    #[test]
+    fn hover_range_math() {
+        check_hover_range(
+            r#"
+fn f() { let expr = $01 + 2 * 3$0 }
+"#,
+            expect![[r#"
+            ```rust
+            i32
+            ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn f() { let expr = 1 $0+ 2 * $03 }
+"#,
+            expect![[r#"
+            ```rust
+            i32
+            ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn f() { let expr = 1 + $02 * 3$0 }
+"#,
+            expect![[r#"
+            ```rust
+            i32
+            ```"#]],
+        );
+    }
+
+    #[test]
+    fn hover_range_arrays() {
+        check_hover_range(
+            r#"
+fn f() { let expr = $0[1, 2, 3, 4]$0 }
+"#,
+            expect![[r#"
+            ```rust
+            [i32; 4]
+            ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn f() { let expr = [1, 2, $03, 4]$0 }
+"#,
+            expect![[r#"
+            ```rust
+            [i32; 4]
+            ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn f() { let expr = [1, 2, $03$0, 4] }
+"#,
+            expect![[r#"
+            ```rust
+            i32
+            ```"#]],
+        );
+    }
+
+    #[test]
+    fn hover_range_functions() {
+        check_hover_range(
+            r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
+"#,
+            expect![[r#"
+            ```rust
+            fn f<i32>(&[i32])
+            ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn f<T>(a: &[T]) { }
+fn b() { f($0&[1, 2, 3, 4, 5]$0); }
+"#,
+            expect![[r#"
+            ```rust
+            &[i32; 5]
+            ```"#]],
+        );
+    }
+
+    #[test]
+    fn hover_range_shows_nothing_when_invalid() {
+        check_hover_range_no_results(
+            r#"
+fn f<T>(a: &[T]) { }
+fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
+"#,
+        );
+
+        check_hover_range_no_results(
+            r#"
+fn f<T>$0(a: &[T]) { }
+fn b() { f(&[1, 2, 3,$0 4, 5]); }
+"#,
+        );
+
+        check_hover_range_no_results(
+            r#"
+fn $0f() { let expr = [1, 2, 3, 4]$0 }
+"#,
+        );
+    }
+
+    #[test]
+    fn hover_range_shows_unit_for_statements() {
+        check_hover_range(
+            r#"
+fn f<T>(a: &[T]) { }
+fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
+"#,
+            expect![[r#"
+            ```rust
+            ()
+            ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn f() { let expr$0 = $0[1, 2, 3, 4] }
+"#,
+            expect![[r#"
+            ```rust
+            ()
+            ```"#]],
+        );
+    }
+
+    #[test]
+    fn hover_range_for_pat() {
+        check_hover_range(
+            r#"
+fn foo() {
+    let $0x$0 = 0;
+}
+"#,
+            expect![[r#"
+                ```rust
+                i32
+                ```"#]],
+        );
+
+        check_hover_range(
+            r#"
+fn foo() {
+    let $0x$0 = "";
+}
+"#,
+            expect![[r#"
+                ```rust
+                &str
+                ```"#]],
+        );
+    }
+
+    #[test]
+    fn hover_range_shows_coercions_if_applicable_expr() {
+        check_hover_range(
+            r#"
+fn foo() {
+    let x: &u32 = $0&&&&&0$0;
+}
+"#,
+            expect![[r#"
+                ```text
+                Type:       &&&&&u32
+                Coerced to:     &u32
+                ```
+            "#]],
+        );
+        check_hover_range(
+            r#"
+fn foo() {
+    let x: *const u32 = $0&0$0;
+}
+"#,
+            expect![[r#"
+                ```text
+                Type:             &u32
+                Coerced to: *const u32
+                ```
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_range_shows_type_actions() {
+        check_actions(
+            r#"
+struct Foo;
+fn foo() {
+    let x: &Foo = $0&&&&&Foo$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_try_expr_res() {
+        check_hover_range(
+            r#"
+//- minicore:result
+struct FooError;
+
+fn foo() -> Result<(), FooError> {
+    Ok($0Result::<(), FooError>::Ok(())?$0)
+}
+"#,
+            expect![[r#"
+                ```rust
+                ()
+                ```"#]],
+        );
+        check_hover_range(
+            r#"
+//- minicore:result
+struct FooError;
+struct BarError;
+
+fn foo() -> Result<(), FooError> {
+    Ok($0Result::<(), BarError>::Ok(())?$0)
+}
+"#,
+            expect![[r#"
+                ```text
+                Try Error Type: BarError
+                Propagated as:  FooError
+                ```
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_try_expr() {
+        check_hover_range(
+            r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Looooong> {
+    $0NotResult((), Short)?$0;
+}
+"#,
+            expect![[r#"
+                ```text
+                Try Target Type:    NotResult<(), Short>
+                Propagated as:   NotResult<(), Looooong>
+                ```
+            "#]],
+        );
+        check_hover_range(
+            r#"
+struct NotResult<T, U>(T, U);
+struct Short;
+struct Looooong;
+
+fn foo() -> NotResult<(), Short> {
+    $0NotResult((), Looooong)?$0;
+}
+"#,
+            expect![[r#"
+                ```text
+                Try Target Type: NotResult<(), Looooong>
+                Propagated as:      NotResult<(), Short>
+                ```
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_try_expr_option() {
+        cov_mark::check!(hover_try_expr_opt_opt);
+        check_hover_range(
+            r#"
+//- minicore: option, try
+
+fn foo() -> Option<()> {
+    $0Some(0)?$0;
+    None
+}
+"#,
+            expect![[r#"
+                ```rust
+                <Option<i32> as Try>::Output
+                ```"#]],
+        );
+    }
+
+    #[test]
+    fn hover_deref_expr() {
+        check_hover_range(
+            r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+    value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.value
+    }
+}
+
+fn foo() {
+    let x = DerefExample { value: 0 };
+    let y: i32 = $0*x$0;
+}
+"#,
+            expect![[r#"
+                ```text
+                Dereferenced from: DerefExample<i32>
+                To type:                         i32
+                ```
+            "#]],
+        );
+    }
+
+    #[test]
+    fn hover_deref_expr_with_coercion() {
+        check_hover_range(
+            r#"
+//- minicore: deref
+use core::ops::Deref;
+
+struct DerefExample<T> {
+    value: T
+}
+
+impl<T> Deref for DerefExample<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.value
+    }
+}
+
+fn foo() {
+    let x = DerefExample { value: &&&&&0 };
+    let y: &i32 = $0*x$0;
+}
+"#,
+            expect![[r#"
+                ```text
+                Dereferenced from: DerefExample<&&&&&i32>
+                To type:                         &&&&&i32
+                Coerced to:                          &i32
+                ```
+            "#]],
+        );
+    }
 }