rust/crates/ide/src/hover.rs
2021-09-22 11:37:26 +03:30

5052 lines
125 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::{collections::HashSet, iter, ops::ControlFlow};
use either::Either;
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
use ide_db::{
base_db::{FileRange, SourceDatabase},
defs::{Definition, NameClass, NameRefClass},
helpers::{
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
pick_best_token, try_resolve_derive_input_at, FamousDefs,
},
RootDatabase,
};
use itertools::Itertools;
use stdx::format_to;
use syntax::{
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
};
use crate::{
display::{macro_label, TryToNav},
doc_links::{
doc_attributes, extract_definitions_from_docs, remove_links, resolve_doc_path_for_def,
rewrite_links,
},
markdown_remove::remove_markdown,
markup::Markup,
runnables::{runnable_fn, runnable_mod},
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub links_in_hover: bool,
pub documentation: Option<HoverDocFormat>,
}
impl HoverConfig {
fn markdown(&self) -> bool {
matches!(self.documentation, Some(HoverDocFormat::Markdown))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HoverDocFormat {
Markdown,
PlainText,
}
#[derive(Debug, Clone)]
pub enum HoverAction {
Runnable(Runnable),
Implementation(FilePosition),
Reference(FilePosition),
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,
pub nav: NavigationTarget,
}
/// Contains the results when hovering over an item
#[derive(Debug, Default)]
pub struct HoverResult {
pub markup: Markup,
pub actions: Vec<HoverAction>,
}
// Feature: Hover
//
// 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,
FileRange { file_id, range }: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
let sema = hir::Semantics::new(db);
let file = sema.parse(file_id).syntax().clone();
if !range.is_empty() {
return hover_ranged(&file, range, &sema, config);
}
let offset = range.start();
let 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 mut seen = HashSet::default();
let mut fallback = None;
// attributes, require special machinery as they are mere ident tokens
let descend_macros = sema.descend_into_macros_many(token.clone());
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);
// derives
} else {
let def =
try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro);
if let Some(def) = def {
if let Some(hover) = hover_for_definition(
&sema,
file_id,
def,
&token.parent().unwrap(),
config,
) {
return Some(RangeInfo::new(token.text_range(), hover));
}
}
}
}
}
}
descend_macros
.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 = 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)
}
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 = find_definition(sema, node).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
}
});
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(|| sema.original_range(&node).range);
return Some(ControlFlow::Break(RangeInfo::new(range, 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 expr_or_pat = match_ast! {
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_many` failed to resolve.
// (e.g expanding a builtin macro). So we give up here.
ast::MacroCall(_it) => return None,
_ => return None,
}
};
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()) {
return None;
}
let (is_clippy, lints) = match &*path {
"feature" => (false, FEATURES),
"allow" | "deny" | "forbid" | "warn" => {
let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
.filter(|t| t.kind() == T![:])
.and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
.filter(|t| t.kind() == T![:])
.and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
.map_or(false, |t| {
t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
});
if is_clippy {
(true, CLIPPY_LINTS)
} else {
(false, DEFAULT_LINTS)
}
}
_ => return None,
};
let tmp;
let needle = if is_clippy {
tmp = format!("clippy::{}", token.text());
&tmp
} else {
&*token.text()
};
let lint =
lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
Some(RangeInfo::new(
token.text_range(),
HoverResult {
markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
..Default::default()
},
))
}
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
fn to_action(nav_target: NavigationTarget) -> HoverAction {
HoverAction::Implementation(FilePosition {
file_id: nav_target.file_id,
offset: nav_target.focus_or_full_range().start(),
})
}
let adt = match def {
Definition::ModuleDef(hir::ModuleDef::Trait(it)) => {
return it.try_to_nav(db).map(to_action)
}
Definition::ModuleDef(hir::ModuleDef::Adt(it)) => Some(it),
Definition::SelfType(it) => it.self_ty(db).as_adt(),
_ => None,
}?;
adt.try_to_nav(db).map(to_action)
}
fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
match def {
Definition::ModuleDef(hir::ModuleDef::Function(it)) => {
it.try_to_nav(db).map(|nav_target| {
HoverAction::Reference(FilePosition {
file_id: nav_target.file_id,
offset: nav_target.focus_or_full_range().start(),
})
})
}
_ => None,
}
}
fn runnable_action(
sema: &hir::Semantics<RootDatabase>,
def: Definition,
file_id: FileId,
) -> Option<HoverAction> {
match def {
Definition::ModuleDef(it) => match it {
hir::ModuleDef::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
hir::ModuleDef::Function(func) => {
let src = func.source(sema.db)?;
if src.file_id != file_id.into() {
cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr);
return None;
}
runnable_fn(sema, func).map(HoverAction::Runnable)
}
_ => None,
},
_ => None,
}
}
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) {
targets.push(item);
}
};
if let Definition::GenericParam(hir::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::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
Definition::Field(field) => field.ty(db),
_ => return None,
};
walk_and_push_ty(db, &ty, &mut push_new_def);
}
Some(HoverAction::goto_type_from_targets(db, 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> {
let mut buf = String::new();
if let Some(mod_path) = mod_path {
if !mod_path.is_empty() {
format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
}
}
format_to!(buf, "```rust\n{}\n```", desc);
if let Some(doc) = docs {
format_to!(buf, "\n___\n\n{}", doc);
}
Some(buf.into())
}
fn process_markup(
db: &RootDatabase,
def: Definition,
markup: &Markup,
config: &HoverConfig,
) -> Markup {
let markup = markup.as_str();
let markup = if !config.markdown() {
remove_markdown(markup)
} else if config.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)),
Definition::Local(l) => l.parent(db).name(db),
Definition::ModuleDef(md) => match md {
hir::ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
},
hir::ModuleDef::Variant(e) => Some(e.parent_enum(db).name(db)),
_ => None,
},
_ => None,
}
.map(|name| name.to_string())
}
fn render_path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
let crate_name =
db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
let module_path = module
.path_to_root(db)
.into_iter()
.rev()
.flat_map(|it| it.name(db).map(|name| name.to_string()));
crate_name.into_iter().chain(module_path).chain(item_name).join("::")
}
fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
if let Definition::GenericParam(_) = def {
return None;
}
def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
}
pub(crate) fn find_definition<'a>(
sema: &'a Semantics<RootDatabase>,
node: &SyntaxNode,
) -> impl Iterator<Item = Definition> + 'a {
iter::once(node.clone()).flat_map(move |node| {
match_ast! {
match node {
ast::Name(name) => {
let class = if let Some(x) = NameClass::classify(&sema, &name) {
x
} else {
return vec![];
};
match class {
NameClass::Definition(it) | NameClass::ConstReference(it) => vec![it],
NameClass::PatFieldShorthand { local_def, field_ref } => vec![Definition::Local(local_def), Definition::Field(field_ref)],
}
},
ast::NameRef(name_ref) => {
let class = if let Some(x) = NameRefClass::classify(sema, &name_ref) {
x
} else {
return vec![];
};
match class {
NameRefClass::Definition(def) => vec![def],
NameRefClass::FieldShorthand { local_ref, field_ref } => {
vec![Definition::Field(field_ref), Definition::Local(local_ref)]
}
}
},
ast::Lifetime(lifetime) => {
(if let Some(x) = NameClass::classify_lifetime(&sema, &lifetime) {
NameClass::defined(x)
} else {
NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
NameRefClass::Definition(it) => Some(it),
_ => None,
})
}).into_iter().collect()
},
_ => vec![],
}
}
})
}
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>,
config: &HoverConfig,
) -> Option<Markup> {
let mod_path = definition_mod_path(db, &def);
let (label, docs) = match def {
Definition::Macro(it) => (
match &it.source(db)?.value {
Either::Left(mac) => macro_label(mac),
Either::Right(mac_fn) => fn_as_proc_macro_label(mac_fn),
},
it.attrs(db).docs(),
),
Definition::Field(def) => label_and_docs(db, def),
Definition::ModuleDef(it) => match it {
hir::ModuleDef::Module(it) => label_and_docs(db, it),
hir::ModuleDef::Function(it) => label_and_docs(db, it),
hir::ModuleDef::Adt(it) => label_and_docs(db, it),
hir::ModuleDef::Variant(it) => label_and_docs(db, it),
hir::ModuleDef::Const(it) => label_and_docs(db, it),
hir::ModuleDef::Static(it) => label_and_docs(db, it),
hir::ModuleDef::Trait(it) => label_and_docs(db, it),
hir::ModuleDef::TypeAlias(it) => label_and_docs(db, it),
hir::ModuleDef::BuiltinType(it) => {
return famous_defs
.and_then(|fd| hover_for_builtin(fd, it))
.or_else(|| Some(Markup::fenced_block(&it.name())))
}
},
Definition::Local(it) => return hover_for_local(it, db),
Definition::SelfType(impl_def) => {
impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
}
Definition::GenericParam(it) => label_and_docs(db, it),
Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db))),
};
return hover_markup(
docs.filter(|_| config.documentation.is_some()).map(Into::into),
label,
mod_path,
);
fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
where
D: HasAttrs + HirDisplay,
{
let label = def.display(db).to_string();
let docs = def.attrs(db).docs();
(label, docs)
}
}
fn hover_for_local(it: hir::Local, db: &RootDatabase) -> Option<Markup> {
let ty = it.ty(db);
let ty = ty.display(db);
let is_mut = if it.is_mut(db) { "mut " } else { "" };
let desc = match it.source(db).value {
Either::Left(ident) => {
let name = it.name(db).unwrap();
let let_kw = if ident
.syntax()
.parent()
.map_or(false, |p| p.kind() == LET_STMT || p.kind() == CONDITION)
{
"let "
} else {
""
};
format!("{}{}{}: {}", let_kw, is_mut, name, ty)
}
Either::Right(_) => format!("{}self: {}", is_mut, ty),
};
hover_markup(None, desc, None)
}
fn hover_for_keyword(
sema: &Semantics<RootDatabase>,
config: &HoverConfig,
token: &SyntaxToken,
) -> Option<RangeInfo<HoverResult>> {
if !token.kind().is_keyword() || !config.documentation.is_some() {
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()), token.text().into(), None)?,
config,
);
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()), 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))
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use ide_db::base_db::{FileLoader, FileRange};
use syntax::TextRange;
use crate::{fixture, hover::HoverDocFormat, HoverConfig};
fn check_hover_no_result(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
documentation: Some(HoverDocFormat::Markdown),
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap();
assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap());
}
fn check(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
documentation: Some(HoverDocFormat::Markdown),
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content[hover.range];
let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
expect.assert_eq(&actual)
}
fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: false,
documentation: Some(HoverDocFormat::Markdown),
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content[hover.range];
let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
expect.assert_eq(&actual)
}
fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
documentation: Some(HoverDocFormat::PlainText),
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
.unwrap()
.unwrap();
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content[hover.range];
let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
expect.assert_eq(&actual)
}
fn check_actions(ra_fixture: &str, expect: Expect) {
let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
let hover = analysis
.hover(
&HoverConfig {
links_in_hover: true,
documentation: Some(HoverDocFormat::Markdown),
},
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(
r#"
pub fn foo() -> u32 { 1 }
fn main() {
let foo_test = foo()$0;
}
"#,
expect![[r#"
*foo()*
```rust
u32
```
"#]],
);
}
#[test]
fn hover_remove_markdown_if_configured() {
check_hover_no_markdown(
r#"
pub fn foo() -> u32 { 1 }
fn main() {
let foo_test = foo()$0;
}
"#,
expect![[r#"
*foo()*
u32
"#]],
);
}
#[test]
fn hover_shows_long_type_of_an_expression() {
check(
r#"
struct Scan<A, B, C> { a: A, b: B, c: C }
struct Iter<I> { inner: I }
enum Option<T> { Some(T), None }
struct OtherStruct<T> { i: T }
fn scan<A, B, C>(a: A, b: B, c: C) -> Iter<Scan<OtherStruct<A>, B, C>> {
Iter { inner: Scan { a, b, c } }
}
fn main() {
let num: i32 = 55;
let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> Option<u32> {
Option::Some(*memo + value)
};
let number = 5u32;
let mut iter$0 = scan(OtherStruct { i: num }, closure, number);
}
"#,
expect![[r#"
*iter*
```rust
let mut iter: Iter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> Option<u32>, u32>>
```
"#]],
);
}
#[test]
fn hover_shows_fn_signature() {
// Single file with result
check(
r#"
pub fn foo() -> u32 { 1 }
fn main() { let foo_test = fo$0o(); }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub fn foo() -> u32
```
"#]],
);
// Multiple candidates but results are ambiguous.
check(
r#"
//- /a.rs
pub fn foo() -> u32 { 1 }
//- /b.rs
pub fn foo() -> &str { "" }
//- /c.rs
pub fn foo(a: u32, b: u32) {}
//- /main.rs
mod a;
mod b;
mod c;
fn main() { let foo_test = fo$0o(); }
"#,
expect![[r#"
*foo*
```rust
{unknown}
```
"#]],
);
// 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]
fn hover_shows_fn_signature_with_type_params() {
check(
r#"
pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
fn main() { let foo_test = fo$0o(); }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub fn foo<'a, T>(b: &'a T) -> &'a str
where
T: AsRef<str>,
```
"#]],
);
}
#[test]
fn hover_shows_fn_signature_on_fn_name() {
check(
r#"
pub fn foo$0(a: u32, b: u32) -> u32 {}
fn main() { }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub fn foo(a: u32, b: u32) -> u32
```
"#]],
);
}
#[test]
fn hover_shows_fn_doc() {
check(
r#"
/// # Example
/// ```
/// # use std::path::Path;
/// #
/// foo(Path::new("hello, world!"))
/// ```
pub fn foo$0(_: &Path) {}
fn main() { }
"#,
expect![[r##"
*foo*
```rust
test
```
```rust
pub fn foo(_: &Path)
```
---
# Example
```
# use std::path::Path;
#
foo(Path::new("hello, world!"))
```
"##]],
);
}
#[test]
fn hover_shows_fn_doc_attr_raw_string() {
check(
r##"
#[doc = r#"Raw string doc attr"#]
pub fn foo$0(_: &Path) {}
fn main() { }
"##,
expect![[r##"
*foo*
```rust
test
```
```rust
pub fn foo(_: &Path)
```
---
Raw string doc attr
"##]],
);
}
#[test]
fn hover_shows_struct_field_info() {
// Hovering over the field when instantiating
check(
r#"
struct Foo { field_a: u32 }
fn main() {
let foo = Foo { field_a$0: 0, };
}
"#,
expect![[r#"
*field_a*
```rust
test::Foo
```
```rust
field_a: u32
```
"#]],
);
// Hovering over the field in the definition
check(
r#"
struct Foo { field_a$0: u32 }
fn main() {
let foo = Foo { field_a: 0 };
}
"#,
expect![[r#"
*field_a*
```rust
test::Foo
```
```rust
field_a: u32
```
"#]],
);
}
#[test]
fn hover_const_static() {
check(
r#"const foo$0: u32 = 123;"#,
expect![[r#"
*foo*
```rust
test
```
```rust
const foo: u32
```
"#]],
);
check(
r#"static foo$0: u32 = 456;"#,
expect![[r#"
*foo*
```rust
test
```
```rust
static foo: u32
```
"#]],
);
}
#[test]
fn hover_default_generic_types() {
check(
r#"
struct Test<K, T = u8> { k: K, t: T }
fn main() {
let zz$0 = Test { t: 23u8, k: 33 };
}"#,
expect![[r#"
*zz*
```rust
let zz: Test<i32, u8>
```
"#]],
);
}
#[test]
fn hover_some() {
check(
r#"
enum Option<T> { Some(T) }
use Option::Some;
fn main() { So$0me(12); }
"#,
expect![[r#"
*Some*
```rust
test::Option
```
```rust
Some(T)
```
"#]],
);
check(
r#"
enum Option<T> { Some(T) }
use Option::Some;
fn main() { let b$0ar = Some(12); }
"#,
expect![[r#"
*bar*
```rust
let bar: Option<i32>
```
"#]],
);
}
#[test]
fn hover_enum_variant() {
check(
r#"
enum Option<T> {
/// The None variant
Non$0e
}
"#,
expect![[r#"
*None*
```rust
test::Option
```
```rust
None
```
---
The None variant
"#]],
);
check(
r#"
enum Option<T> {
/// The Some variant
Some(T)
}
fn main() {
let s = Option::Som$0e(12);
}
"#,
expect![[r#"
*Some*
```rust
test::Option
```
```rust
Some(T)
```
---
The Some variant
"#]],
);
}
#[test]
fn hover_for_local_variable() {
check(
r#"fn func(foo: i32) { fo$0o; }"#,
expect![[r#"
*foo*
```rust
foo: i32
```
"#]],
)
}
#[test]
fn hover_for_local_variable_pat() {
check(
r#"fn func(fo$0o: i32) {}"#,
expect![[r#"
*foo*
```rust
foo: i32
```
"#]],
)
}
#[test]
fn hover_local_var_edge() {
check(
r#"fn func(foo: i32) { if true { $0foo; }; }"#,
expect![[r#"
*foo*
```rust
foo: i32
```
"#]],
)
}
#[test]
fn hover_for_param_edge() {
check(
r#"fn func($0foo: i32) {}"#,
expect![[r#"
*foo*
```rust
foo: i32
```
"#]],
)
}
#[test]
fn hover_for_param_with_multiple_traits() {
check(
r#"
//- minicore: sized
trait Deref {
type Target: ?Sized;
}
trait DerefMut {
type Target: ?Sized;
}
fn f(_x$0: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
expect![[r#"
*_x*
```rust
_x: impl Deref<Target = u8> + DerefMut<Target = u8>
```
"#]],
)
}
#[test]
fn test_hover_infer_associated_method_result() {
check(
r#"
struct Thing { x: u32 }
impl Thing {
fn new() -> Thing { Thing { x: 0 } }
}
fn main() { let foo_$0test = Thing::new(); }
"#,
expect![[r#"
*foo_test*
```rust
let foo_test: Thing
```
"#]],
)
}
#[test]
fn test_hover_infer_associated_method_exact() {
check(
r#"
mod wrapper {
struct Thing { x: u32 }
impl Thing {
fn new() -> Thing { Thing { x: 0 } }
}
}
fn main() { let foo_test = wrapper::Thing::new$0(); }
"#,
expect![[r#"
*new*
```rust
test::wrapper::Thing
```
```rust
fn new() -> Thing
```
"#]],
)
}
#[test]
fn test_hover_infer_associated_const_in_pattern() {
check(
r#"
struct X;
impl X {
const C: u32 = 1;
}
fn main() {
match 1 {
X::C$0 => {},
2 => {},
_ => {}
};
}
"#,
expect![[r#"
*C*
```rust
test
```
```rust
const C: u32
```
"#]],
)
}
#[test]
fn test_hover_self() {
check(
r#"
struct Thing { x: u32 }
impl Thing {
fn new() -> Self { Self$0 { x: 0 } }
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
struct Thing
```
"#]],
);
check(
r#"
struct Thing { x: u32 }
impl Thing {
fn new() -> Self$0 { Self { x: 0 } }
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
struct Thing
```
"#]],
);
check(
r#"
enum Thing { A }
impl Thing {
pub fn new() -> Self$0 { Thing::A }
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
enum Thing
```
"#]],
);
check(
r#"
enum Thing { A }
impl Thing {
pub fn thing(a: Self$0) {}
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
enum Thing
```
"#]],
);
}
#[test]
fn test_hover_shadowing_pat() {
check(
r#"
fn x() {}
fn y() {
let x = 0i32;
x$0;
}
"#,
expect![[r#"
*x*
```rust
let x: i32
```
"#]],
)
}
#[test]
fn test_hover_macro_invocation() {
check(
r#"
macro_rules! foo { () => {} }
fn f() { fo$0o!(); }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
macro_rules! foo
```
"#]],
)
}
#[test]
fn test_hover_macro2_invocation() {
check(
r#"
/// foo bar
///
/// foo bar baz
macro foo() {}
fn f() { fo$0o!(); }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
macro foo
```
---
foo bar
foo bar baz
"#]],
)
}
#[test]
fn test_hover_tuple_field() {
check(
r#"struct TS(String, i32$0);"#,
expect![[r#"
*i32*
```rust
i32
```
"#]],
)
}
#[test]
fn test_hover_through_macro() {
check(
r#"
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
fn foo() {}
id! {
fn bar() { fo$0o(); }
}
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
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(
r#"
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
fn foo(bar:u32) { let a = id!(ba$0r); }
"#,
expect![[r#"
*bar*
```rust
bar: u32
```
"#]],
);
}
#[test]
fn test_hover_through_expr_in_macro_recursive() {
check(
r#"
macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
fn foo(bar:u32) { let a = id!(ba$0r); }
"#,
expect![[r#"
*bar*
```rust
bar: u32
```
"#]],
);
}
#[test]
fn test_hover_through_func_in_macro_recursive() {
check(
r#"
macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
fn bar() -> u32 { 0 }
fn foo() { let a = id!([0u32, bar($0)] ); }
"#,
expect![[r#"
*bar()*
```rust
u32
```
"#]],
);
}
#[test]
fn test_hover_through_literal_string_in_macro() {
check(
r#"
macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } }
fn foo() {
let mastered_for_itunes = "";
let _ = arr!("Tr$0acks", &mastered_for_itunes);
}
"#,
expect![[r#"
*"Tracks"*
```rust
&str
```
"#]],
);
}
#[test]
fn test_hover_through_assert_macro() {
check(
r#"
#[rustc_builtin_macro]
macro_rules! assert {}
fn bar() -> bool { true }
fn foo() {
assert!(ba$0r());
}
"#,
expect![[r#"
*bar*
```rust
test
```
```rust
fn bar() -> bool
```
"#]],
);
}
#[test]
fn test_hover_through_literal_string_in_builtin_macro() {
check_hover_no_result(
r#"
#[rustc_builtin_macro]
macro_rules! format {}
fn foo() {
format!("hel$0lo {}", 0);
}
"#,
);
}
#[test]
fn test_hover_non_ascii_space_doc() {
check(
"
/// <- `\u{3000}` here
fn foo() { }
fn bar() { fo$0o(); }
",
expect![[r#"
*foo*
```rust
test
```
```rust
fn foo()
```
---
\<- ` ` here
"#]],
);
}
#[test]
fn test_hover_function_show_qualifiers() {
check(
r#"async fn foo$0() {}"#,
expect![[r#"
*foo*
```rust
test
```
```rust
async fn foo()
```
"#]],
);
check(
r#"pub const unsafe fn foo$0() {}"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub const unsafe fn foo()
```
"#]],
);
// Top level `pub(crate)` will be displayed as no visibility.
check(
r#"mod m { pub(crate) async unsafe extern "C" fn foo$0() {} }"#,
expect![[r#"
*foo*
```rust
test::m
```
```rust
pub(crate) async unsafe extern "C" fn foo()
```
"#]],
);
}
#[test]
fn test_hover_trait_show_qualifiers() {
check_actions(
r"unsafe trait foo$0() {}",
expect![[r#"
[
Implementation(
FilePosition {
file_id: FileId(
0,
),
offset: 13,
},
),
]
"#]],
);
}
#[test]
fn test_hover_extern_crate() {
check(
r#"
//- /main.rs crate:main deps:std
extern crate st$0d;
//- /std/lib.rs crate:std
//! Standard library for this test
//!
//! Printed?
//! abc123
"#,
expect![[r#"
*std*
```rust
extern crate std
```
---
Standard library for this test
Printed?
abc123
"#]],
);
check(
r#"
//- /main.rs crate:main deps:std
extern crate std as ab$0c;
//- /std/lib.rs crate:std
//! Standard library for this test
//!
//! Printed?
//! abc123
"#,
expect![[r#"
*abc*
```rust
extern crate std
```
---
Standard library for this test
Printed?
abc123
"#]],
);
}
#[test]
fn test_hover_mod_with_same_name_as_function() {
check(
r#"
use self::m$0y::Bar;
mod my { pub struct Bar; }
fn my() {}
"#,
expect![[r#"
*my*
```rust
test
```
```rust
mod my
```
"#]],
);
}
#[test]
fn test_hover_struct_doc_comment() {
check(
r#"
/// This is an example
/// multiline doc
///
/// # Example
///
/// ```
/// let five = 5;
///
/// assert_eq!(6, my_crate::add_one(5));
/// ```
struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r##"
*Bar*
```rust
test
```
```rust
struct Bar
```
---
This is an example
multiline doc
# Example
```
let five = 5;
assert_eq!(6, my_crate::add_one(5));
```
"##]],
);
}
#[test]
fn test_hover_struct_doc_attr() {
check(
r#"
#[doc = "bar docs"]
struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
struct Bar
```
---
bar docs
"#]],
);
}
#[test]
fn test_hover_struct_doc_attr_multiple_and_mixed() {
check(
r#"
/// bar docs 0
#[doc = "bar docs 1"]
#[doc = "bar docs 2"]
struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
struct Bar
```
---
bar docs 0
bar docs 1
bar docs 2
"#]],
);
}
#[test]
fn test_hover_external_url() {
check(
r#"
pub struct Foo;
/// [external](https://www.google.com)
pub struct B$0ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[external](https://www.google.com)
"#]],
);
}
// Check that we don't rewrite links which we can't identify
#[test]
fn test_hover_unknown_target() {
check(
r#"
pub struct Foo;
/// [baz](Baz)
pub struct B$0ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[baz](Baz)
"#]],
);
}
#[test]
fn test_hover_no_links() {
check_hover_no_links(
r#"
/// Test cases:
/// case 1. bare URL: https://www.example.com/
/// case 2. inline URL with title: [example](https://www.example.com/)
/// 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. reference: [example][example]
/// case 8. collapsed link: [example][]
/// case 9. shortcut link: [example]
/// case 10. inline without URL: [example]()
/// 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]()
/// case 16. just escaped text: \[foo]
/// case 17. inline link: [Foo](foo::Foo)
///
/// [`Result`]: ../../std/result/enum.Result.html
/// [^example]: https://www.example.com/
pub fn fo$0o() {}
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
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 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. reference: example
case 8. collapsed link: example
case 9. shortcut link: example
case 10. inline without URL: example
case 11. reference: foo
case 12. reference: foo
case 13. collapsed link: foo
case 14. shortcut link: foo
case 15. inline without URL: foo
case 16. just escaped text: \[foo\]
case 17. inline link: Foo
[^example]: https://www.example.com/
"#]],
);
}
#[test]
fn test_hover_macro_generated_struct_fn_doc_comment() {
cov_mark::check!(hover_macro_generated_struct_fn_doc_comment);
check(
r#"
macro_rules! bar {
() => {
struct Bar;
impl Bar {
/// Do the foo
fn foo(&self) {}
}
}
}
bar!();
fn foo() { let bar = Bar; bar.fo$0o(); }
"#,
expect![[r#"
*foo*
```rust
test::Bar
```
```rust
fn foo(&self)
```
---
Do the foo
"#]],
);
}
#[test]
fn test_hover_macro_generated_struct_fn_doc_attr() {
cov_mark::check!(hover_macro_generated_struct_fn_doc_attr);
check(
r#"
macro_rules! bar {
() => {
struct Bar;
impl Bar {
#[doc = "Do the foo"]
fn foo(&self) {}
}
}
}
bar!();
fn foo() { let bar = Bar; bar.fo$0o(); }
"#,
expect![[r#"
*foo*
```rust
test::Bar
```
```rust
fn foo(&self)
```
---
Do the foo
"#]],
);
}
#[test]
fn test_hover_trait_has_impl_action() {
check_actions(
r#"trait foo$0() {}"#,
expect![[r#"
[
Implementation(
FilePosition {
file_id: FileId(
0,
),
offset: 6,
},
),
]
"#]],
);
}
#[test]
fn test_hover_struct_has_impl_action() {
check_actions(
r"struct foo$0() {}",
expect![[r#"
[
Implementation(
FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
),
]
"#]],
);
}
#[test]
fn test_hover_union_has_impl_action() {
check_actions(
r#"union foo$0() {}"#,
expect![[r#"
[
Implementation(
FilePosition {
file_id: FileId(
0,
),
offset: 6,
},
),
]
"#]],
);
}
#[test]
fn test_hover_enum_has_impl_action() {
check_actions(
r"enum foo$0() { A, B }",
expect![[r#"
[
Implementation(
FilePosition {
file_id: FileId(
0,
),
offset: 5,
},
),
]
"#]],
);
}
#[test]
fn test_hover_self_has_impl_action() {
check_actions(
r#"struct foo where Self$0:;"#,
expect![[r#"
[
Implementation(
FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
),
]
"#]],
);
}
#[test]
fn test_hover_test_has_action() {
check_actions(
r#"
#[test]
fn foo_$0test() {}
"#,
expect![[r#"
[
Reference(
FilePosition {
file_id: FileId(
0,
),
offset: 11,
},
),
Runnable(
Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..24,
focus_range: 11..19,
name: "foo_test",
kind: Function,
},
kind: Test {
test_id: Path(
"foo_test",
),
attr: TestAttr {
ignore: false,
},
},
cfg: None,
},
),
]
"#]],
);
}
#[test]
fn test_hover_test_mod_has_action() {
check_actions(
r#"
mod tests$0 {
#[test]
fn foo_test() {}
}
"#,
expect![[r#"
[
Runnable(
Runnable {
use_name_in_title: false,
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..46,
focus_range: 4..9,
name: "tests",
kind: Module,
description: "mod tests",
},
kind: TestMod {
path: "tests",
},
cfg: None,
},
),
]
"#]],
);
}
#[test]
fn test_hover_struct_has_goto_type_action() {
check_actions(
r#"
struct S{ f1: u32 }
fn main() { let s$0t = S{ f1:0 }; }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..19,
focus_range: 7..8,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_generic_struct_has_goto_type_actions() {
check_actions(
r#"
struct Arg(u32);
struct S<T>{ f1: T }
fn main() { let s$0t = S{ f1:Arg(0) }; }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 17..37,
focus_range: 24..25,
name: "S",
kind: Struct,
description: "struct S<T>",
},
},
HoverGotoTypeData {
mod_path: "test::Arg",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..16,
focus_range: 7..10,
name: "Arg",
kind: Struct,
description: "struct Arg",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_generic_struct_has_flattened_goto_type_actions() {
check_actions(
r#"
struct Arg(u32);
struct S<T>{ f1: T }
fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 17..37,
focus_range: 24..25,
name: "S",
kind: Struct,
description: "struct S<T>",
},
},
HoverGotoTypeData {
mod_path: "test::Arg",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..16,
focus_range: 7..10,
name: "Arg",
kind: Struct,
description: "struct Arg",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_tuple_has_goto_type_actions() {
check_actions(
r#"
struct A(u32);
struct B(u32);
mod M {
pub struct C(u32);
}
fn main() { let s$0t = (A(1), B(2), M::C(3) ); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::A",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..14,
focus_range: 7..8,
name: "A",
kind: Struct,
description: "struct A",
},
},
HoverGotoTypeData {
mod_path: "test::B",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 15..29,
focus_range: 22..23,
name: "B",
kind: Struct,
description: "struct B",
},
},
HoverGotoTypeData {
mod_path: "test::M::C",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 42..60,
focus_range: 53..54,
name: "C",
kind: Struct,
description: "pub struct C",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_return_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo() -> impl Foo {}
fn main() { let s$0t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_generic_return_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo<T> {}
struct S;
fn foo() -> impl Foo<S> {}
fn main() { let s$0t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo<T>",
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..25,
focus_range: 23..24,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_return_impl_traits_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
trait Bar {}
fn foo() -> impl Foo + Bar {}
fn main() { let s$0t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 13..25,
focus_range: 19..22,
name: "Bar",
kind: Trait,
description: "trait Bar",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_generic_return_impl_traits_has_goto_type_action() {
check_actions(
r#"
trait Foo<T> {}
trait Bar<T> {}
struct S1 {}
struct S2 {}
fn foo() -> impl Foo<S1> + Bar<S2> {}
fn main() { let s$0t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo<T>",
},
},
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..31,
focus_range: 22..25,
name: "Bar",
kind: Trait,
description: "trait Bar<T>",
},
},
HoverGotoTypeData {
mod_path: "test::S1",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 32..44,
focus_range: 39..41,
name: "S1",
kind: Struct,
description: "struct S1",
},
},
HoverGotoTypeData {
mod_path: "test::S2",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 45..57,
focus_range: 52..54,
name: "S2",
kind: Struct,
description: "struct S2",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_arg_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo(ar$0g: &impl Foo) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_arg_impl_traits_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
trait Bar<T> {}
struct S{}
fn foo(ar$0g: &impl Foo + Bar<S>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 13..28,
focus_range: 19..22,
name: "Bar",
kind: Trait,
description: "trait Bar<T>",
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 29..39,
focus_range: 36..37,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_async_block_impl_trait_has_goto_type_action() {
check_actions(
r#"
//- minicore: future
struct S;
fn foo() {
let fo$0o = async { S };
}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "core::future::Future",
nav: NavigationTarget {
file_id: FileId(
1,
),
full_range: 254..436,
focus_range: 293..299,
name: "Future",
kind: Trait,
description: "pub trait Future",
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..9,
focus_range: 7..8,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo<T> {}
struct S {}
fn foo(ar$0g: &impl Foo<S>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo<T>",
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..27,
focus_range: 23..24,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_dyn_return_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
struct S;
impl Foo for S {}
struct B<T>{}
fn foo() -> B<dyn Foo> {}
fn main() { let s$0t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::B",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 42..55,
focus_range: 49..50,
name: "B",
kind: Struct,
description: "struct B<T>",
},
},
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_dyn_arg_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo(ar$0g: &dyn Foo) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_generic_dyn_arg_has_goto_type_action() {
check_actions(
r#"
trait Foo<T> {}
struct S {}
fn foo(ar$0g: &dyn Foo<S>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo<T>",
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..27,
focus_range: 23..24,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_goto_type_action_links_order() {
check_actions(
r#"
trait ImplTrait<T> {}
trait DynTrait<T> {}
struct B<T> {}
struct S {}
fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::ImplTrait",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..21,
focus_range: 6..15,
name: "ImplTrait",
kind: Trait,
description: "trait ImplTrait<T>",
},
},
HoverGotoTypeData {
mod_path: "test::B",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 43..57,
focus_range: 50..51,
name: "B",
kind: Struct,
description: "struct B<T>",
},
},
HoverGotoTypeData {
mod_path: "test::DynTrait",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 22..42,
focus_range: 28..36,
name: "DynTrait",
kind: Trait,
description: "trait DynTrait<T>",
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 58..69,
focus_range: 65..66,
name: "S",
kind: Struct,
description: "struct S",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_associated_type_has_goto_type_action() {
check_actions(
r#"
trait Foo {
type Item;
fn get(self) -> Self::Item {}
}
struct Bar{}
struct S{}
impl Foo for S { type Item = Bar; }
fn test() -> impl Foo { S {} }
fn main() { let s$0t = test().get(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..62,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_const_param_has_goto_type_action() {
check_actions(
r#"
struct Bar;
struct Foo<const BAR: Bar>;
impl<const BAR: Bar> Foo<BAR$0> {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..11,
focus_range: 7..10,
name: "Bar",
kind: Struct,
description: "struct Bar",
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_type_param_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo<T: Foo>(t: T$0){}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: 6..9,
name: "Foo",
kind: Trait,
description: "trait Foo",
},
},
],
),
]
"#]],
);
}
#[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(
r#"
//- /lib.rs crate:name-with-dashes
pub mod wrapper {
pub struct Thing { x: u32 }
impl Thing {
pub fn new() -> Thing { Thing { x: 0 } }
}
}
//- /main.rs crate:main deps:name-with-dashes
fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
"#,
expect![[r#"
*new*
```rust
name_with_dashes::wrapper::Thing
```
```rust
pub fn new() -> Thing
```
"#]],
)
}
#[test]
fn hover_field_pat_shorthand_ref_match_ergonomics() {
check(
r#"
struct S {
f: i32,
}
fn main() {
let s = S { f: 0 };
let S { f$0 } = &s;
}
"#,
expect![[r#"
*f*
```rust
f: &i32
```
"#]],
);
}
#[test]
fn hover_self_param_shows_type() {
check(
r#"
struct Foo {}
impl Foo {
fn bar(&sel$0f) {}
}
"#,
expect![[r#"
*self*
```rust
self: &Foo
```
"#]],
);
}
#[test]
fn hover_self_param_shows_type_for_arbitrary_self_type() {
check(
r#"
struct Arc<T>(T);
struct Foo {}
impl Foo {
fn bar(sel$0f: Arc<Foo>) {}
}
"#,
expect![[r#"
*self*
```rust
self: Arc<Foo>
```
"#]],
);
}
#[test]
fn hover_doc_outer_inner() {
check(
r#"
/// Be quick;
mod Foo$0 {
//! time is mana
/// This comment belongs to the function
fn foo() {}
}
"#,
expect![[r#"
*Foo*
```rust
test
```
```rust
mod Foo
```
---
Be quick;
time is mana
"#]],
);
}
#[test]
fn hover_doc_outer_inner_attribue() {
check(
r#"
#[doc = "Be quick;"]
mod Foo$0 {
#![doc = "time is mana"]
#[doc = "This comment belongs to the function"]
fn foo() {}
}
"#,
expect![[r#"
*Foo*
```rust
test
```
```rust
mod Foo
```
---
Be quick;
time is mana
"#]],
);
}
#[test]
fn hover_doc_block_style_indentend() {
check(
r#"
/**
foo
```rust
let x = 3;
```
*/
fn foo$0() {}
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
fn foo()
```
---
foo
```rust
let x = 3;
```
"#]],
);
}
#[test]
fn hover_comments_dont_highlight_parent() {
cov_mark::check!(no_highlight_on_comment_hover);
check_hover_no_result(
r#"
fn no_hover() {
// no$0hover
}
"#,
);
}
#[test]
fn hover_label() {
check(
r#"
fn foo() {
'label$0: loop {}
}
"#,
expect![[r#"
*'label*
```rust
'label
```
"#]],
);
}
#[test]
fn hover_lifetime() {
check(
r#"fn foo<'lifetime>(_: &'lifetime$0 ()) {}"#,
expect![[r#"
*'lifetime*
```rust
'lifetime
```
"#]],
);
}
#[test]
fn hover_type_param() {
check(
r#"
//- minicore: sized
struct Foo<T>(T);
trait TraitA {}
trait TraitB {}
impl<T: TraitA + TraitB> Foo<T$0> where T: Sized {}
"#,
expect![[r#"
*T*
```rust
T: TraitA + TraitB
```
"#]],
);
check(
r#"
//- minicore: sized
struct Foo<T>(T);
impl<T> Foo<T$0> {}
"#,
expect![[r#"
*T*
```rust
T
```
"#]],
);
// lifetimes bounds arent being tracked yet
check(
r#"
//- minicore: sized
struct Foo<T>(T);
impl<T: 'static> Foo<T$0> {}
"#,
expect![[r#"
*T*
```rust
T
```
"#]],
);
}
#[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(
r#"
struct Foo<const LEN: usize>;
impl<const LEN: usize> Foo<LEN$0> {}
"#,
expect![[r#"
*LEN*
```rust
const LEN: usize
```
"#]],
);
}
#[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
```
---
This is a doc
"#]],
);
}
#[test]
fn hover_mod_def() {
check(
r#"
//- /main.rs
mod foo$0;
//- /foo.rs
//! For the horde!
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
mod 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
mod bar
```
---
But this should appear
"#]],
)
}
#[test]
fn hover_keyword() {
check(
r#"
//- /main.rs crate:main deps:std
fn f() { retur$0n; }
//- /libstd.rs crate:std
/// Docs for return_keyword
mod return_keyword {}
"#,
expect![[r#"
*return*
```rust
return
```
---
Docs for return_keyword
"#]],
);
}
#[test]
fn hover_builtin() {
check(
r#"
//- /main.rs crate:main deps:std
cosnt _: &str$0 = ""; }
//- /libstd.rs crate:std
/// Docs for prim_str
mod prim_str {}
"#,
expect![[r#"
*str*
```rust
str
```
---
Docs for prim_str
"#]],
);
}
#[test]
fn hover_macro_expanded_function() {
check(
r#"
struct S<'a, T>(&'a T);
trait Clone {}
macro_rules! foo {
() => {
fn bar<'t, T: Clone + 't>(s: &mut S<'t, T>, t: u32) -> *mut u32 where
't: 't + 't,
for<'a> T: Clone + 'a
{ 0 as _ }
};
}
foo!();
fn main() {
bar$0;
}
"#,
expect![[r#"
*bar*
```rust
test
```
```rust
fn bar<'t, T>(s: &mut S<'t, T>, t: u32) -> *mut u32
where
T: Clone + 't,
't: 't + 't,
for<'a> T: Clone + 'a,
```
"#]],
)
}
#[test]
fn hover_intra_doc_links() {
check(
r#"
pub mod theitem {
/// This is the item. Cool!
pub struct TheItem;
}
/// Gives you a [`TheItem$0`].
///
/// [`TheItem`]: theitem::TheItem
pub fn gimme() -> theitem::TheItem {
theitem::TheItem
}
"#,
expect![[r#"
*[`TheItem`]*
```rust
test::theitem
```
```rust
pub struct TheItem
```
---
This is the item. Cool!
"#]],
);
}
#[test]
fn hover_generic_assoc() {
check(
r#"
fn foo<T: A>() where T::Assoc$0: {}
trait A {
type Assoc;
}"#,
expect![[r#"
*Assoc*
```rust
test
```
```rust
type Assoc
```
"#]],
);
check(
r#"
fn foo<T: A>() {
let _: <T>::Assoc$0;
}
trait A {
type Assoc;
}"#,
expect![[r#"
*Assoc*
```rust
test
```
```rust
type Assoc
```
"#]],
);
check(
r#"
trait A where
Self::Assoc$0: ,
{
type Assoc;
}"#,
expect![[r#"
*Assoc*
```rust
test
```
```rust
type Assoc
```
"#]],
);
}
#[test]
fn string_shadowed_with_inner_items() {
check(
r#"
//- /main.rs crate:main deps:alloc
/// Custom `String` type.
struct String;
fn f() {
let _: String$0;
fn inner() {}
}
//- /alloc.rs crate:alloc
#[prelude_import]
pub use string::*;
mod string {
/// This is `alloc::String`.
pub struct String;
}
"#,
expect![[r#"
*String*
```rust
main
```
```rust
struct String
```
---
Custom `String` type.
"#]],
)
}
#[test]
fn function_doesnt_shadow_crate_in_use_tree() {
check(
r#"
//- /main.rs crate:main deps:foo
use foo$0::{foo};
//- /foo.rs crate:foo
pub fn foo() {}
"#,
expect![[r#"
*foo*
```rust
extern crate foo
```
"#]],
)
}
#[test]
fn hover_feature() {
check(
r#"#![feature(box_syntax$0)]"#,
expect![[r##"
*box_syntax*
```
box_syntax
```
___
# `box_syntax`
The tracking issue for this feature is: [#49733]
[#49733]: https://github.com/rust-lang/rust/issues/49733
See also [`box_patterns`](box-patterns.md)
------------------------
Currently the only stable way to create a `Box` is via the `Box::new` method.
Also it is not possible in stable Rust to destructure a `Box` in a match
pattern. The unstable `box` keyword can be used to create a `Box`. An example
usage would be:
```rust
#![feature(box_syntax)]
fn main() {
let b = box 5;
}
```
"##]],
)
}
#[test]
fn hover_lint() {
check(
r#"#![allow(arithmetic_overflow$0)]"#,
expect![[r#"
*arithmetic_overflow*
```
arithmetic_overflow
```
___
arithmetic operation overflows
"#]],
)
}
#[test]
fn hover_clippy_lint() {
check(
r#"#![allow(clippy::almost_swapped$0)]"#,
expect![[r#"
*almost_swapped*
```
clippy::almost_swapped
```
___
Checks for `foo = bar; bar = foo` sequences.
"#]],
)
}
#[test]
fn hover_attr_path_qualifier() {
cov_mark::check!(name_ref_classify_attr_path_qualifier);
check(
r#"
//- /foo.rs crate:foo
//- /lib.rs crate:main.rs deps:foo
#[fo$0o::bar()]
struct Foo;
"#,
expect![[r#"
*foo*
```rust
extern crate foo
```
"#]],
)
}
#[test]
fn hover_rename() {
check(
r#"
use self as foo$0;
"#,
expect![[r#"
*foo*
```rust
extern crate test
```
"#]],
);
check(
r#"
mod bar {}
use bar::{self as foo$0};
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
mod bar
```
"#]],
);
check(
r#"
mod bar {
use super as foo$0;
}
"#,
expect![[r#"
*foo*
```rust
extern crate test
```
"#]],
);
check(
r#"
use crate as foo$0;
"#,
expect![[r#"
*foo*
```rust
extern crate test
```
"#]],
);
}
// FIXME: wrong range in macros. `es! ` should be `Copy`
#[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#"
*es! *
```rust
test
```
```rust
pub macro Copy
```
"#]],
);
}
#[test]
fn hover_derive_input() {
check(
r#"
#[rustc_builtin_macro]
pub macro Copy {}
#[derive(Copy$0)]
struct Foo;
"#,
expect![[r#"
*Copy*
```rust
test
```
```rust
pub macro Copy
```
"#]],
);
check(
r#"
mod foo {
#[rustc_builtin_macro]
pub macro Copy {}
}
#[derive(foo::Copy$0)]
struct Foo;
"#,
expect![[r#"
*Copy*
```rust
test
```
```rust
pub macro Copy
```
"#]],
);
}
#[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
```
"#]],
);
}
}