rust-analyzer/crates/ide/src/hover.rs
2020-11-29 22:49:12 +01:00

3354 lines
88 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 hir::{
Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
Module, ModuleDef, ModuleSource, Semantics,
};
use ide_db::base_db::SourceDatabase;
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
RootDatabase,
};
use itertools::Itertools;
use stdx::format_to;
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
use test_utils::mark;
use crate::{
display::{macro_label, ShortLabel, ToNav, TryToNav},
doc_links::{remove_links, rewrite_links},
markdown_remove::remove_markdown,
markup::Markup,
runnables::runnable,
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub implementations: bool,
pub run: bool,
pub debug: bool,
pub goto_type_def: bool,
pub links_in_hover: bool,
pub markdown: bool,
}
impl Default for HoverConfig {
fn default() -> Self {
Self {
implementations: true,
run: true,
debug: true,
goto_type_def: true,
links_in_hover: true,
markdown: true,
}
}
}
impl HoverConfig {
pub const NO_ACTIONS: Self = Self {
implementations: false,
run: false,
debug: false,
goto_type_def: false,
links_in_hover: true,
markdown: true,
};
pub fn any(&self) -> bool {
self.implementations || self.runnable() || self.goto_type_def
}
pub fn none(&self) -> bool {
!self.any()
}
pub fn runnable(&self) -> bool {
self.run || self.debug
}
}
#[derive(Debug, Clone)]
pub enum HoverAction {
Runnable(Runnable),
Implementaion(FilePosition),
GoToType(Vec<HoverGotoTypeData>),
}
#[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 type of an expression or documentation for definition when "focusing" code.
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
pub(crate) fn hover(
db: &RootDatabase,
position: FilePosition,
links_in_hover: bool,
markdown: bool,
) -> Option<RangeInfo<HoverResult>> {
let sema = Semantics::new(db);
let file = sema.parse(position.file_id).syntax().clone();
let token = pick_best(file.token_at_offset(position.offset))?;
let token = sema.descend_into_macros(token);
let mut res = HoverResult::default();
let node = token.parent();
let definition = match_ast! {
match node {
ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)),
ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
_ => None,
}
};
if let Some(definition) = definition {
if let Some(markup) = hover_for_definition(db, definition) {
let markup = if !markdown {
remove_markdown(&markup.as_str())
} else if links_in_hover {
rewrite_links(db, &markup.as_str(), &definition)
} else {
remove_links(&markup.as_str())
};
res.markup = Markup::from(markup);
if let Some(action) = show_implementations_action(db, definition) {
res.actions.push(action);
}
if let Some(action) = runnable_action(&sema, definition, position.file_id) {
res.actions.push(action);
}
if let Some(action) = goto_type_action(db, definition) {
res.actions.push(action);
}
let range = sema.original_range(&node).range;
return Some(RangeInfo::new(range, res));
}
}
let node = token.ancestors().find(|n| {
ast::Expr::can_cast(n.kind())
|| ast::Pat::can_cast(n.kind())
|| ast::SelfParam::can_cast(n.kind())
})?;
let ty = match_ast! {
match node {
ast::Expr(it) => sema.type_of_expr(&it)?,
ast::Pat(it) => sema.type_of_pat(&it)?,
ast::SelfParam(self_param) => sema.type_of_self(&self_param)?,
// If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
// (e.g expanding a builtin macro). So we give up here.
ast::MacroCall(_it) => return None,
_ => return None,
}
};
res.markup = if markdown {
Markup::fenced_block(&ty.display(db))
} else {
ty.display(db).to_string().into()
};
let range = sema.original_range(&node).range;
Some(RangeInfo::new(range, res))
}
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
fn to_action(nav_target: NavigationTarget) -> HoverAction {
HoverAction::Implementaion(FilePosition {
file_id: nav_target.file_id,
offset: nav_target.focus_or_full_range().start(),
})
}
match def {
Definition::ModuleDef(it) => match it {
ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
_ => None,
},
_ => None,
}
}
fn runnable_action(
sema: &Semantics<RootDatabase>,
def: Definition,
file_id: FileId,
) -> Option<HoverAction> {
match def {
Definition::ModuleDef(it) => match it {
ModuleDef::Module(it) => match it.definition_source(sema.db).value {
ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
.map(|it| HoverAction::Runnable(it)),
_ => None,
},
ModuleDef::Function(it) => {
let src = it.source(sema.db);
if src.file_id != file_id.into() {
mark::hit!(hover_macro_generated_struct_fn_doc_comment);
mark::hit!(hover_macro_generated_struct_fn_doc_attr);
return None;
}
runnable(&sema, src.value.syntax().clone(), file_id)
.map(|it| HoverAction::Runnable(it))
}
_ => None,
},
_ => None,
}
}
fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
match def {
Definition::Local(it) => {
let mut targets: Vec<ModuleDef> = Vec::new();
let mut push_new_def = |item: ModuleDef| {
if !targets.contains(&item) {
targets.push(item);
}
};
it.ty(db).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());
}
});
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::GoToType(targets))
}
_ => None,
}
}
fn hover_markup(
docs: Option<String>,
desc: Option<String>,
mod_path: Option<String>,
) -> Option<Markup> {
match desc {
Some(desc) => {
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())
}
None => docs.map(Markup::from),
}
}
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 {
ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
AssocItemContainer::Trait(t) => Some(t.name(db)),
AssocItemContainer::ImplDef(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
},
ModuleDef::EnumVariant(e) => Some(e.parent_enum(db).name(db)),
_ => None,
},
_ => None,
}
.map(|name| name.to_string())
}
fn render_path(db: &RootDatabase, module: 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> {
def.module(db).map(|module| render_path(db, module, definition_owner_name(db, def)))
}
fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
let mod_path = definition_mod_path(db, &def);
return match def {
Definition::Macro(it) => {
let src = it.source(db);
let docs = Documentation::from_ast(&src.value).map(Into::into);
hover_markup(docs, Some(macro_label(&src.value)), mod_path)
}
Definition::Field(it) => {
let src = it.source(db);
match src.value {
FieldSource::Named(it) => {
let docs = Documentation::from_ast(&it).map(Into::into);
hover_markup(docs, it.short_label(), mod_path)
}
_ => None,
}
}
Definition::ModuleDef(it) => match it {
ModuleDef::Module(it) => match it.definition_source(db).value {
ModuleSource::Module(it) => {
let docs = Documentation::from_ast(&it).map(Into::into);
hover_markup(docs, it.short_label(), mod_path)
}
ModuleSource::SourceFile(it) => {
let docs = Documentation::from_ast(&it).map(Into::into);
hover_markup(docs, it.short_label(), mod_path)
}
},
ModuleDef::Function(it) => from_def_source(db, it, mod_path),
ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it, mod_path),
ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it, mod_path),
ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it, mod_path),
ModuleDef::EnumVariant(it) => from_def_source(db, it, mod_path),
ModuleDef::Const(it) => from_def_source(db, it, mod_path),
ModuleDef::Static(it) => from_def_source(db, it, mod_path),
ModuleDef::Trait(it) => from_def_source(db, it, mod_path),
ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
ModuleDef::BuiltinType(it) => return Some(it.to_string().into()),
},
Definition::Local(it) => return Some(Markup::fenced_block(&it.ty(db).display(db))),
Definition::SelfType(impl_def) => {
impl_def.target_ty(db).as_adt().and_then(|adt| match adt {
Adt::Struct(it) => from_def_source(db, it, mod_path),
Adt::Union(it) => from_def_source(db, it, mod_path),
Adt::Enum(it) => from_def_source(db, it, mod_path),
})
}
Definition::TypeParam(_) => {
// FIXME: Hover for generic param
None
}
};
fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<Markup>
where
D: HasSource<Ast = A>,
A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner,
{
let src = def.source(db);
let docs = Documentation::from_ast(&src.value).map(Into::into);
hover_markup(docs, src.value.short_label(), mod_path)
}
}
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
return tokens.max_by_key(priority);
fn priority(n: &SyntaxToken) -> usize {
match n.kind() {
IDENT | INT_NUMBER => 3,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
_ => 1,
}
}
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use ide_db::base_db::FileLoader;
use crate::fixture;
use super::*;
fn check_hover_no_result(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
assert!(analysis.hover(position, true, true).unwrap().is_none());
}
fn check(ra_fixture: &str, expect: Expect) {
let (analysis, position) = fixture::position(ra_fixture);
let hover = analysis.hover(position, true, true).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(position, false, true).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(position, true, false).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, position) = fixture::position(ra_fixture);
let hover = analysis.hover(position, true, true).unwrap().unwrap();
expect.assert_debug_eq(&hover.info.actions)
}
#[test]
fn hover_shows_type_of_an_expression() {
check(
r#"
pub fn foo() -> u32 { 1 }
fn main() {
let foo_test = foo()<|>;
}
"#,
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()<|>;
}
"#,
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<|> = scan(OtherStruct { i: num }, closure, number);
}
"#,
expect![[r#"
*iter*
```rust
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<|>o(); }
"#,
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<|>o(); }
"#,
expect![[r#"
*foo*
```rust
{unknown}
```
"#]],
);
}
#[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<|>o(); }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str
```
"#]],
);
}
#[test]
fn hover_shows_fn_signature_on_fn_name() {
check(
r#"
pub fn foo<|>(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<|>(_: &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<|>(_: &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, };
}
"#,
expect![[r#"
*field_a*
```rust
test::Foo
```
```rust
field_a: u32
```
"#]],
);
// Hovering over the field in the definition
check(
r#"
struct Foo { field_a<|>: 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<|>: u32 = 123;"#,
expect![[r#"
*foo*
```rust
test
```
```rust
const foo: u32 = 123
```
"#]],
);
check(
r#"static foo<|>: 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<|> = Test { t: 23u8, k: 33 };
}"#,
expect![[r#"
*zz*
```rust
Test<i32, u8>
```
"#]],
);
}
#[test]
fn hover_some() {
check(
r#"
enum Option<T> { Some(T) }
use Option::Some;
fn main() { So<|>me(12); }
"#,
expect![[r#"
*Some*
```rust
test::Option
```
```rust
Some
```
"#]],
);
check(
r#"
enum Option<T> { Some(T) }
use Option::Some;
fn main() { let b<|>ar = Some(12); }
"#,
expect![[r#"
*bar*
```rust
Option<i32>
```
"#]],
);
}
#[test]
fn hover_enum_variant() {
check(
r#"
enum Option<T> {
/// The None variant
Non<|>e
}
"#,
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<|>e(12);
}
"#,
expect![[r#"
*Some*
```rust
test::Option
```
```rust
Some
```
---
The Some variant
"#]],
);
}
#[test]
fn hover_for_local_variable() {
check(
r#"fn func(foo: i32) { fo<|>o; }"#,
expect![[r#"
*foo*
```rust
i32
```
"#]],
)
}
#[test]
fn hover_for_local_variable_pat() {
check(
r#"fn func(fo<|>o: i32) {}"#,
expect![[r#"
*foo*
```rust
i32
```
"#]],
)
}
#[test]
fn hover_local_var_edge() {
check(
r#"fn func(foo: i32) { if true { <|>foo; }; }"#,
expect![[r#"
*foo*
```rust
i32
```
"#]],
)
}
#[test]
fn hover_for_param_edge() {
check(
r#"fn func(<|>foo: i32) {}"#,
expect![[r#"
*foo*
```rust
i32
```
"#]],
)
}
#[test]
fn hover_for_param_with_multiple_traits() {
check(
r#"trait Deref {
type Target: ?Sized;
}
trait DerefMut {
type Target: ?Sized;
}
fn f(_x<|>: impl Deref<Target=u8> + DerefMut<Target=u8>) {}"#,
expect![[r#"
*_x*
```rust
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_<|>test = Thing::new(); }
"#,
expect![[r#"
*foo_test*
```rust
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<|>(); }
"#,
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<|> => {},
2 => {},
_ => {}
};
}
"#,
expect![[r#"
*C*
```rust
test
```
```rust
const C: u32 = 1
```
"#]],
)
}
#[test]
fn test_hover_self() {
check(
r#"
struct Thing { x: u32 }
impl Thing {
fn new() -> Self { Self<|> { x: 0 } }
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
struct Thing
```
"#]],
);
check(
r#"
struct Thing { x: u32 }
impl Thing {
fn new() -> Self<|> { Self { x: 0 } }
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
struct Thing
```
"#]],
);
check(
r#"
enum Thing { A }
impl Thing {
pub fn new() -> Self<|> { Thing::A }
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
enum Thing
```
"#]],
);
check(
r#"
enum Thing { A }
impl Thing {
pub fn thing(a: Self<|>) {}
}
"#,
expect![[r#"
*Self*
```rust
test
```
```rust
enum Thing
```
"#]],
);
}
#[test]
fn test_hover_shadowing_pat() {
check(
r#"
fn x() {}
fn y() {
let x = 0i32;
x<|>;
}
"#,
expect![[r#"
*x*
```rust
i32
```
"#]],
)
}
#[test]
fn test_hover_macro_invocation() {
check(
r#"
macro_rules! foo { () => {} }
fn f() { fo<|>o!(); }
"#,
expect![[r#"
*foo*
```rust
test
```
```rust
macro_rules! foo
```
"#]],
)
}
#[test]
fn test_hover_tuple_field() {
check(
r#"struct TS(String, i32<|>);"#,
expect![[r#"
*i32*
i32
"#]],
)
}
#[test]
fn test_hover_through_macro() {
check(
r#"
macro_rules! id { ($($tt:tt)*) => { $($tt)* } }
fn foo() {}
id! {
fn bar() { fo<|>o(); }
}
"#,
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<|>r); }
"#,
expect![[r#"
*bar*
```rust
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<|>r); }
"#,
expect![[r#"
*bar*
```rust
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(<|>)] ); }
"#,
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<|>acks", &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<|>r());
}
"#,
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<|>lo {}", 0);
}
"#,
);
}
#[test]
fn test_hover_non_ascii_space_doc() {
check(
"
/// <- `\u{3000}` here
fn foo() { }
fn bar() { fo<|>o(); }
",
expect![[r#"
*foo*
```rust
test
```
```rust
fn foo()
```
---
\<- ` ` here
"#]],
);
}
#[test]
fn test_hover_function_show_qualifiers() {
check(
r#"async fn foo<|>() {}"#,
expect![[r#"
*foo*
```rust
test
```
```rust
async fn foo()
```
"#]],
);
check(
r#"pub const unsafe fn foo<|>() {}"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub const unsafe fn foo()
```
"#]],
);
check(
r#"pub(crate) async unsafe extern "C" fn foo<|>() {}"#,
expect![[r#"
*foo*
```rust
test
```
```rust
pub(crate) async unsafe extern "C" fn foo()
```
"#]],
);
}
#[test]
fn test_hover_trait_show_qualifiers() {
check_actions(
r"unsafe trait foo<|>() {}",
expect![[r#"
[
Implementaion(
FilePosition {
file_id: FileId(
0,
),
offset: 13,
},
),
]
"#]],
);
}
#[test]
fn test_hover_extern_crate() {
check(
r#"
//- /main.rs crate:main deps:std
extern crate st<|>d;
//- /std/lib.rs crate:std
//! Standard library for this test
//!
//! Printed?
//! abc123
"#,
expect![[r#"
*std*
Standard library for this test
Printed?
abc123
"#]],
);
check(
r#"
//- /main.rs crate:main deps:std
extern crate std as ab<|>c;
//- /std/lib.rs crate:std
//! Standard library for this test
//!
//! Printed?
//! abc123
"#,
expect![[r#"
*abc*
Standard library for this test
Printed?
abc123
"#]],
);
}
#[test]
fn test_hover_mod_with_same_name_as_function() {
check(
r#"
use self::m<|>y::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#"
/// bar docs
struct Bar;
fn foo() { let bar = Ba<|>r; }
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
struct Bar
```
---
bar docs
"#]],
);
}
#[test]
fn test_hover_struct_doc_attr() {
check(
r#"
#[doc = "bar docs"]
struct Bar;
fn foo() { let bar = Ba<|>r; }
"#,
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<|>r; }
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
struct Bar
```
---
bar docs 0
bar docs 1
bar docs 2
"#]],
);
}
#[test]
fn test_hover_path_link() {
check(
r#"
pub struct Foo;
/// [Foo](struct.Foo.html)
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[Foo](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_path_link_no_strip() {
check(
r#"
pub struct Foo;
/// [struct Foo](struct.Foo.html)
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[struct Foo](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[ignore = "path based links currently only support documentation on ModuleDef items"]
#[test]
fn test_hover_path_link_field() {
check(
r#"
pub struct Foo;
pub struct Bar {
/// [Foo](struct.Foo.html)
fie<|>ld: ()
}
"#,
expect![[r#"
*field*
```rust
test::Bar
```
```rust
field: ()
```
---
[Foo](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link() {
check(
r#"
pub mod foo {
pub struct Foo;
}
/// [Foo](foo::Foo)
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[Foo](https://docs.rs/test/*/test/foo/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_html_root_url() {
check(
r#"
#![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)]
pub mod foo {
pub struct Foo;
}
/// [Foo](foo::Foo)
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[Foo](https://example.com/test/foo/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_shortlink() {
check(
r#"
pub struct Foo;
/// [Foo]
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[Foo](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_shortlink_code() {
check(
r#"
pub struct Foo;
/// [`Foo`]
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_namespaced() {
check(
r#"
pub struct Foo;
fn Foo() {}
/// [Foo()]
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[Foo](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_shortlink_namspaced_code() {
check(
r#"
pub struct Foo;
/// [`struct Foo`]
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_shortlink_namspaced_code_with_at() {
check(
r#"
pub struct Foo;
/// [`struct@Foo`]
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_intra_link_reference() {
check(
r#"
pub struct Foo;
/// [my Foo][foo]
///
/// [foo]: Foo
pub struct B<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[my Foo](https://docs.rs/test/*/test/struct.Foo.html)
"#]],
);
}
#[test]
fn test_hover_external_url() {
check(
r#"
pub struct Foo;
/// [external](https://www.google.com)
pub struct B<|>ar
"#,
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<|>ar
"#,
expect![[r#"
*Bar*
```rust
test
```
```rust
pub struct Bar
```
---
[baz](Baz)
"#]],
);
}
#[test]
fn test_doc_links_enum_variant() {
check(
r#"
enum E {
/// [E]
V<|> { field: i32 }
}
"#,
expect![[r#"
*V*
```rust
test::E
```
```rust
V
```
---
[E](https://docs.rs/test/*/test/enum.E.html)
"#]],
);
}
#[test]
fn test_doc_links_field() {
check(
r#"
struct S {
/// [`S`]
field<|>: i32
}
"#,
expect![[r#"
*field*
```rust
test::S
```
```rust
field: i32
```
---
[`S`](https://docs.rs/test/*/test/struct.S.html)
"#]],
);
}
#[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 refrence: [`Result`]
/// case 4. code refrence but miss footnote: [`String`]
/// case 5. autolink: <http://www.example.com/>
/// case 6. email address: <test@example.com>
/// case 7. refrence: [example][example]
/// case 8. collapsed link: [example][]
/// case 9. shortcut link: [example]
/// case 10. inline without URL: [example]()
/// case 11. refrence: [foo][foo]
/// case 12. refrence: [foo][bar]
/// case 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<|>o() {}
"#,
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 refrence: `Result`
case 4. code refrence but miss footnote: `String`
case 5. autolink: http://www.example.com/
case 6. email address: test@example.com
case 7. refrence: example
case 8. collapsed link: example
case 9. shortcut link: example
case 10. inline without URL: example
case 11. refrence: foo
case 12. refrence: foo
case 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() {
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<|>o(); }
"#,
expect![[r#"
*foo*
```rust
test::Bar
```
```rust
fn foo(&self)
```
---
Do the foo
"#]],
);
}
#[test]
fn test_hover_macro_generated_struct_fn_doc_attr() {
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<|>o(); }
"#,
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<|>() {}"#,
expect![[r#"
[
Implementaion(
FilePosition {
file_id: FileId(
0,
),
offset: 6,
},
),
]
"#]],
);
}
#[test]
fn test_hover_struct_has_impl_action() {
check_actions(
r"struct foo<|>() {}",
expect![[r#"
[
Implementaion(
FilePosition {
file_id: FileId(
0,
),
offset: 7,
},
),
]
"#]],
);
}
#[test]
fn test_hover_union_has_impl_action() {
check_actions(
r#"union foo<|>() {}"#,
expect![[r#"
[
Implementaion(
FilePosition {
file_id: FileId(
0,
),
offset: 6,
},
),
]
"#]],
);
}
#[test]
fn test_hover_enum_has_impl_action() {
check_actions(
r"enum foo<|>() { A, B }",
expect![[r#"
[
Implementaion(
FilePosition {
file_id: FileId(
0,
),
offset: 5,
},
),
]
"#]],
);
}
#[test]
fn test_hover_test_has_action() {
check_actions(
r#"
#[test]
fn foo_<|>test() {}
"#,
expect![[r#"
[
Runnable(
Runnable {
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..24,
focus_range: Some(
11..19,
),
name: "foo_test",
kind: FN,
container_name: None,
description: None,
docs: None,
},
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<|> {
#[test]
fn foo_test() {}
}
"#,
expect![[r#"
[
Runnable(
Runnable {
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..46,
focus_range: Some(
4..9,
),
name: "tests",
kind: MODULE,
container_name: None,
description: None,
docs: None,
},
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<|>t = S{ f1:0 }; }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..19,
focus_range: Some(
7..8,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = S{ f1:Arg(0) }; }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 17..37,
focus_range: Some(
24..25,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::Arg",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..16,
focus_range: Some(
7..10,
),
name: "Arg",
kind: STRUCT,
container_name: None,
description: Some(
"struct Arg",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = 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: Some(
24..25,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::Arg",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..16,
focus_range: Some(
7..10,
),
name: "Arg",
kind: STRUCT,
container_name: None,
description: Some(
"struct Arg",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = (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: Some(
7..8,
),
name: "A",
kind: STRUCT,
container_name: None,
description: Some(
"struct A",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::B",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 15..29,
focus_range: Some(
22..23,
),
name: "B",
kind: STRUCT,
container_name: None,
description: Some(
"struct B",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::M::C",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 42..60,
focus_range: Some(
53..54,
),
name: "C",
kind: STRUCT,
container_name: None,
description: Some(
"pub struct C",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_return_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo() -> impl Foo {}
fn main() { let s<|>t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..25,
focus_range: Some(
23..24,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 13..25,
focus_range: Some(
19..22,
),
name: "Bar",
kind: TRAIT,
container_name: None,
description: Some(
"trait Bar",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..31,
focus_range: Some(
22..25,
),
name: "Bar",
kind: TRAIT,
container_name: None,
description: Some(
"trait Bar",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S1",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 32..44,
focus_range: Some(
39..41,
),
name: "S1",
kind: STRUCT,
container_name: None,
description: Some(
"struct S1",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S2",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 45..57,
focus_range: Some(
52..54,
),
name: "S2",
kind: STRUCT,
container_name: None,
description: Some(
"struct S2",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_arg_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo(ar<|>g: &impl Foo) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_arg_impl_traits_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
trait Bar<T> {}
struct S{}
fn foo(ar<|>g: &impl Foo + Bar<S>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::Bar",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 13..28,
focus_range: Some(
19..22,
),
name: "Bar",
kind: TRAIT,
container_name: None,
description: Some(
"trait Bar",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 29..39,
focus_range: Some(
36..37,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_async_block_impl_trait_has_goto_type_action() {
check_actions(
r#"
struct S;
fn foo() {
let fo<|>o = async { S };
}
#[prelude_import] use future::*;
mod future {
#[lang = "future_trait"]
pub trait Future { type Output; }
}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::future::Future",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 101..163,
focus_range: Some(
140..146,
),
name: "Future",
kind: TRAIT,
container_name: None,
description: Some(
"pub trait Future",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..9,
focus_range: Some(
7..8,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_arg_generic_impl_trait_has_goto_type_action() {
check_actions(
r#"
trait Foo<T> {}
struct S {}
fn foo(ar<|>g: &impl Foo<S>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..27,
focus_range: Some(
23..24,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = foo(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::B",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 42..55,
focus_range: Some(
49..50,
),
name: "B",
kind: STRUCT,
container_name: None,
description: Some(
"struct B",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_dyn_arg_has_goto_type_action() {
check_actions(
r#"
trait Foo {}
fn foo(ar<|>g: &dyn Foo) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..12,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[test]
fn test_hover_generic_dyn_arg_has_goto_type_action() {
check_actions(
r#"
trait Foo<T> {}
struct S {}
fn foo(ar<|>g: &dyn Foo<S>) {}
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..15,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 16..27,
focus_range: Some(
23..24,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>rg: &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: Some(
6..15,
),
name: "ImplTrait",
kind: TRAIT,
container_name: None,
description: Some(
"trait ImplTrait",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::B",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 43..57,
focus_range: Some(
50..51,
),
name: "B",
kind: STRUCT,
container_name: None,
description: Some(
"struct B",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::DynTrait",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 22..42,
focus_range: Some(
28..36,
),
name: "DynTrait",
kind: TRAIT,
container_name: None,
description: Some(
"trait DynTrait",
),
docs: None,
},
},
HoverGotoTypeData {
mod_path: "test::S",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 58..69,
focus_range: Some(
65..66,
),
name: "S",
kind: STRUCT,
container_name: None,
description: Some(
"struct S",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>t = test().get(); }
"#,
expect![[r#"
[
GoToType(
[
HoverGotoTypeData {
mod_path: "test::Foo",
nav: NavigationTarget {
file_id: FileId(
0,
),
full_range: 0..62,
focus_range: Some(
6..9,
),
name: "Foo",
kind: TRAIT,
container_name: None,
description: Some(
"trait Foo",
),
docs: None,
},
},
],
),
]
"#]],
);
}
#[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<|>(); }
"#,
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<|> } = &s;
}
"#,
expect![[r#"
*f*
```rust
&i32
```
"#]],
);
}
#[test]
fn hover_self_param_shows_type() {
check(
r#"
struct Foo {}
impl Foo {
fn bar(&sel<|>f) {}
}
"#,
expect![[r#"
*&self*
```rust
&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<|>f: Arc<Foo>) {}
}
"#,
expect![[r#"
*self: Arc<Foo>*
```rust
Arc<Foo>
```
"#]],
);
}
}