use std::iter::once; use hir::{ Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, ModuleSource, Semantics, Module, Documentation, AttrDef, Crate }; use itertools::Itertools; use ra_db::SourceDatabase; use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use ra_project_model::ProjectWorkspace; use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, ModuleDefId}; use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf, Subtree, SmolStr}; use crate::{ display::{ macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav, }, runnables::runnable, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, }; use test_utils::mark; #[derive(Clone, Debug, PartialEq, Eq)] pub struct HoverConfig { pub implementations: bool, pub run: bool, pub debug: bool, pub goto_type_def: bool, } impl Default for HoverConfig { fn default() -> Self { Self { implementations: true, run: true, debug: true, goto_type_def: true } } } impl HoverConfig { pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false, goto_type_def: false }; 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), } #[derive(Debug, Clone, Eq, PartialEq)] pub struct HoverGotoTypeData { pub mod_path: String, pub nav: NavigationTarget, } use std::path::{Path, PathBuf}; use std::sync::Arc; use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; use comrak::nodes::NodeValue; use url::Url; use ra_ide_db::imports_locator::ImportsLocator; /// Contains the results when hovering over an item #[derive(Debug, Default)] pub struct HoverResult { results: Vec, actions: Vec, } impl HoverResult { pub fn new() -> HoverResult { Self::default() } pub fn extend(&mut self, item: Option) { self.results.extend(item); } pub fn is_empty(&self) -> bool { self.results.is_empty() } pub fn len(&self) -> usize { self.results.len() } pub fn first(&self) -> Option<&str> { self.results.first().map(String::as_str) } pub fn results(&self) -> &[String] { &self.results } pub fn actions(&self) -> &[HoverAction] { &self.actions } pub fn push_action(&mut self, action: HoverAction) { self.actions.push(action); } /// Returns the results converted into markup /// for displaying in a UI /// /// Does not process actions! pub fn to_markup(&self) -> String { self.results.join("\n\n___\n") } } // 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, workspaces: Arc>) -> Option> { 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::new(); if let Some((node, name_kind)) = match_ast! { match (token.parent()) { ast::NameRef(name_ref) => { classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) }, ast::Name(name) => { classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) }, _ => None, } } { let range = sema.original_range(&node).range; let text = hover_text_from_name_kind(db, name_kind.clone()).map(|text| rewrite_links(db, &text, &name_kind, workspaces).unwrap_or(text)); res.extend(text); if !res.is_empty() { if let Some(action) = show_implementations_action(db, name_kind) { res.push_action(action); } if let Some(action) = runnable_action(&sema, name_kind, position.file_id) { res.push_action(action); } if let Some(action) = goto_type_action(db, name_kind) { res.push_action(action); } return Some(RangeInfo::new(range, res)); } } let node = token .ancestors() .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; let ty = match_ast! { match node { ast::MacroCall(_it) => { // 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. return None; }, ast::Expr(it) => { sema.type_of_expr(&it) }, ast::Pat(it) => { sema.type_of_pat(&it) }, _ => None, } }?; res.extend(Some(rust_code_markup(&ty.display(db)))); let range = sema.original_range(&node).range; Some(RangeInfo::new(range, res)) } fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option { fn to_action(nav_target: NavigationTarget) -> HoverAction { HoverAction::Implementaion(FilePosition { file_id: nav_target.file_id(), offset: nav_target.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, def: Definition, file_id: FileId, ) -> Option { 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 { match def { Definition::Local(it) => { let mut targets: Vec = 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: mod_path(db, &it)?, nav: it.try_to_nav(db)?, }) }) .collect(); Some(HoverAction::GoToType(targets)) } _ => None, } } fn hover_text( docs: Option, desc: Option, mod_path: Option, ) -> Option { if let Some(desc) = desc { Some(rust_code_markup_with_doc(&desc, docs.as_deref(), mod_path.as_deref())) } else { docs } } fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option { 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, }, Definition::SelfType(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)), _ => None, } .map(|name| name.to_string()) } fn determine_mod_path(db: &RootDatabase, module: Module, name: Option) -> String { once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) .chain( module .path_to_root(db) .into_iter() .rev() .map(|it| it.name(db).map(|name| name.to_string())), ) .chain(once(name)) .flatten() .join("::") } // returns None only for ModuleDef::BuiltinType fn mod_path(db: &RootDatabase, item: &ModuleDef) -> Option { Some(determine_mod_path(db, item.module(db)?, item.name(db).map(|name| name.to_string()))) } fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option { def.module(db).map(|module| determine_mod_path(db, module, definition_owner_name(db, def))) } fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { 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_text(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_text(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_text(docs, it.short_label(), mod_path) } _ => None, }, 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) => Some(it.to_string()), }, Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))), Definition::TypeParam(_) | Definition::SelfType(_) => { // FIXME: Hover for generic param None } }; fn from_def_source(db: &RootDatabase, def: D, mod_path: Option) -> Option where D: HasSource, A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, { let src = def.source(db); let docs = Documentation::from_ast(&src.value).map(Into::into); hover_text(docs, src.value.short_label(), mod_path) } } /// Rewrite documentation links in markdown to point to local documentation/docs.rs fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition, workspaces: Arc>) -> Option { // FIXME: Fail early if let (Some(name), Some(module)) = (definition.name(db), definition.module(db)) { let krate_name = module.krate().display_name(db)?; let arena = Arena::new(); let doc = parse_document(&arena, markdown, &ComrakOptions::default()); let path = module.path_to_root(db); let mut doc_target_dirs = workspaces .iter() .filter_map(|workspace| if let ProjectWorkspace::Cargo{cargo: cargo_workspace, ..} = workspace {Some(cargo_workspace)} else {None}) .map(|workspace| workspace.workspace_root()) // TODO: `target` is configurable in cargo config, we should respect it .map(|root| root.join("target/doc")); iter_nodes(doc, &|node| { match &mut node.data.borrow_mut().value { &mut NodeValue::Link(ref mut link) => { match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { // If this is a valid absolute URL don't touch it Ok(_) => (), // If contains .html file-based link to new page // If starts with #fragment file-based link to fragment on current page // If contains :: module-based link Err(_) => { let link_str = String::from_utf8(link.url.clone()).unwrap(); let resolved = try_resolve_path(db, &mut doc_target_dirs.clone(), definition, &link_str, UrlMode::Url) .or_else(|| try_resolve_intra(db, &mut doc_target_dirs.clone(), definition, &link_str)); if let Some(resolved) = resolved { link.url = resolved.as_bytes().to_vec(); } } } }, _ => () } }); let mut out = Vec::new(); format_commonmark(doc, &ComrakOptions::default(), &mut out); Some(String::from_utf8(out).unwrap()) } else { // eprintln!("WARN: Unable to determine name or module for hover; link rewriting disabled."); None } } /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`) fn try_resolve_intra(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str) -> Option { None } enum UrlMode { Url, File } /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`) fn try_resolve_path(db: &RootDatabase, doc_target_dirs: impl Iterator, definition: &Definition, link: &str, mode: UrlMode) -> Option { let ns = if let Definition::ModuleDef(moddef) = definition { ItemInNs::Types(moddef.clone().into()) } else { return None; }; let module = definition.module(db)?; let krate = module.krate(); let import_map = db.import_map(krate.into()); // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, // then hope rustdoc was run locally with `--document-private-items` let base = import_map.path_of(ns)?; let mut base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))); match mode { UrlMode::Url => { let root = get_doc_url(db, &krate); let mut base = base.join("/"); if let Some(url) = root { eprintln!("root: {:?} base: {:?} link: {} root&base: {} root&base&link: {}", url, &base, link, url.join(&base).unwrap(), url.join(&base).unwrap().join(link).unwrap()); if link.starts_with("#") { base = base + "/" }; Some(url.join(&base)?.join(link)?.into_string()) } else {None} }, UrlMode::File => { let base = base.collect::(); doc_target_dirs .map(|dir| dir.join(format!("{}", krate.display_name(db).unwrap())).join(base.join("..").join(link))) .inspect(|path| eprintln!("candidate {}", path.display())) .filter(|path| path.exists()) .map(|path| format!("file:///{}", path.display())) // \. is treated as an escape in vscode's markdown hover rendering .map(|path_str| path_str.replace("\\", "/")) .next() } } } /// Try to get the root URL of the documentation of a crate. fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option { // Look for #![doc(html_root_url = "https://docs.rs/...")] let attrs = db.attrs(AttrDef::from(krate.root_module(db)?).into()); let doc_attr_q = attrs.by_key("doc"); let doc_url = if doc_attr_q.exists() { doc_attr_q.tt_values().filter_map(|tt| match tt.token_trees.as_slice() { &[ TokenTree::Leaf(Leaf::Ident(Ident{text: ref ident_text, ..})), TokenTree::Leaf(Leaf::Punct(Punct{r#char: '=', ..})), TokenTree::Leaf(Leaf::Literal(Literal{ref text, ..})) ] if ident_text == "html_root_url" => Some(text), _ => { None } }).next() } else { None }; eprintln!("url {:?}", doc_url); // TODO: It should be possible to fallback to `format!("https://docs.rs/{}/*", crate_name, *)` let url = doc_url.map(|s| s.trim_matches('"').to_owned() + "/").and_then(|s| Url::parse(&s).ok()); eprintln!("url {:?}", url); url } fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) where F : Fn(&'a comrak::nodes::AstNode<'a>) { f(node); for c in node.children() { iter_nodes(c, f); } } fn pick_best(tokens: TokenAtOffset) -> Option { return tokens.max_by_key(priority); fn priority(n: &SyntaxToken) -> usize { match n.kind() { IDENT | INT_NUMBER => 3, L_PAREN | R_PAREN => 2, kind if kind.is_trivia() => 0, _ => 1, } } } #[cfg(test)] mod tests { use super::*; use insta::assert_debug_snapshot; use ra_db::FileLoader; use ra_syntax::TextRange; use crate::mock_analysis::analysis_and_position; fn trim_markup(s: &str) -> &str { s.trim_start_matches("```rust\n").trim_end_matches("\n```") } fn trim_markup_opt(s: Option<&str>) -> Option<&str> { s.map(trim_markup) } fn assert_impl_action(action: &HoverAction, position: u32) { let offset = match action { HoverAction::Implementaion(pos) => pos.offset, it => panic!("Unexpected hover action: {:#?}", it), }; assert_eq!(offset, position.into()); } fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec) { let (analysis, position) = analysis_and_position(fixture); let hover = analysis.hover(position).unwrap().unwrap(); let mut results = Vec::from(hover.info.results()); results.sort(); for (markup, expected) in results.iter().zip(expected.iter().chain(std::iter::repeat(&""))) { assert_eq!(trim_markup(&markup), *expected); } assert_eq!(hover.info.len(), expected.len()); let content = analysis.db.file_text(position.file_id); (content[hover.range].to_string(), hover.info.actions().to_vec()) } fn check_hover_no_result(fixture: &str) { let (analysis, position) = analysis_and_position(fixture); assert!(analysis.hover(position).unwrap().is_none()); } #[test] fn hover_shows_type_of_an_expression() { let (analysis, position) = analysis_and_position( r#" pub fn foo() -> u32 { 1 } fn main() { let foo_test = foo()<|>; } "#, ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(hover.range, TextRange::new(58.into(), 63.into())); assert_eq!(trim_markup_opt(hover.info.first()), Some("u32")); } #[test] fn hover_shows_long_type_of_an_expression() { check_hover_result( r#" //- /main.rs struct Scan { a: A, b: B, c: C, } struct FakeIter { inner: I, } struct OtherStruct { i: T, } enum FakeOption { Some(T), None, } fn scan(a: A, b: B, c: C) -> FakeIter, B, C>> { FakeIter { inner: Scan { a, b, c } } } fn main() { let num: i32 = 55; let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption { FakeOption::Some(*memo + value) }; let number = 5u32; let mut iter<|> = scan(OtherStruct { i: num }, closure, number); } "#, &["FakeIter>, |&mut u32, &u32, &mut u32| -> FakeOption, u32>>"], ); } #[test] fn hover_shows_fn_signature() { // Single file with result check_hover_result( r#" //- /main.rs pub fn foo() -> u32 { 1 } fn main() { let foo_test = fo<|>o(); } "#, &["pub fn foo() -> u32"], ); // Multiple candidates but results are ambiguous. check_hover_result( 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(); } "#, &["{unknown}"], ); } #[test] fn hover_shows_fn_signature_with_type_params() { check_hover_result( r#" //- /main.rs pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str { } fn main() { let foo_test = fo<|>o(); } "#, &["pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str"], ); } #[test] fn hover_shows_fn_signature_on_fn_name() { check_hover_result( r#" //- /main.rs pub fn foo<|>(a: u32, b: u32) -> u32 {} fn main() { } "#, &["pub fn foo(a: u32, b: u32) -> u32"], ); } #[test] fn hover_shows_struct_field_info() { // Hovering over the field when instantiating check_hover_result( r#" //- /main.rs struct Foo { field_a: u32, } fn main() { let foo = Foo { field_a<|>: 0, }; } "#, &["Foo\n```\n\n```rust\nfield_a: u32"], ); // Hovering over the field in the definition check_hover_result( r#" //- /main.rs struct Foo { field_a<|>: u32, } fn main() { let foo = Foo { field_a: 0, }; } "#, &["Foo\n```\n\n```rust\nfield_a: u32"], ); } #[test] fn hover_const_static() { check_hover_result( r#" //- /main.rs const foo<|>: u32 = 0; "#, &["const foo: u32"], ); check_hover_result( r#" //- /main.rs static foo<|>: u32 = 0; "#, &["static foo: u32"], ); } #[test] fn hover_default_generic_types() { check_hover_result( r#" //- /main.rs struct Test { k: K, t: T, } fn main() { let zz<|> = Test { t: 23u8, k: 33 }; }"#, &["Test"], ); } #[test] fn hover_some() { let (analysis, position) = analysis_and_position( " enum Option { Some(T) } use Option::Some; fn main() { So<|>me(12); } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); let (analysis, position) = analysis_and_position( " enum Option { Some(T) } use Option::Some; fn main() { let b<|>ar = Some(12); } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("Option")); } #[test] fn hover_enum_variant() { check_hover_result( r#" //- /main.rs enum Option { /// The None variant Non<|>e } "#, &[" Option ``` ```rust None ``` ___ The None variant " .trim()], ); check_hover_result( r#" //- /main.rs enum Option { /// The Some variant Some(T) } fn main() { let s = Option::Som<|>e(12); } "#, &[" Option ``` ```rust Some ``` ___ The Some variant " .trim()], ); } #[test] fn hover_for_local_variable() { let (analysis, position) = analysis_and_position("fn func(foo: i32) { fo<|>o; }"); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); } #[test] fn hover_for_local_variable_pat() { let (analysis, position) = analysis_and_position("fn func(fo<|>o: i32) {}"); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); } #[test] fn hover_local_var_edge() { let (analysis, position) = analysis_and_position( " fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); } #[test] fn hover_for_param_edge() { let (analysis, position) = analysis_and_position("fn func(<|>foo: i32) {}"); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); } #[test] fn test_hover_infer_associated_method_result() { let (analysis, position) = analysis_and_position( " struct Thing { x: u32 } impl Thing { fn new() -> Thing { Thing { x: 0 } } } fn main() { let foo_<|>test = Thing::new(); } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); } #[test] fn test_hover_infer_associated_method_exact() { let (analysis, position) = analysis_and_position( " mod wrapper { struct Thing { x: u32 } impl Thing { fn new() -> Thing { Thing { x: 0 } } } } fn main() { let foo_test = wrapper::Thing::new<|>(); } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!( trim_markup_opt(hover.info.first()), Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") ); } #[test] fn test_hover_infer_associated_const_in_pattern() { let (analysis, position) = analysis_and_position( " struct X; impl X { const C: u32 = 1; } fn main() { match 1 { X::C<|> => {}, 2 => {}, _ => {} }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); } #[test] fn test_hover_self() { let (analysis, position) = analysis_and_position( " struct Thing { x: u32 } impl Thing { fn new() -> Self { Self<|> { x: 0 } } } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); /* FIXME: revive these tests let (analysis, position) = analysis_and_position( " struct Thing { x: u32 } impl Thing { fn new() -> Self<|> { Self { x: 0 } } } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); let (analysis, position) = analysis_and_position( " enum Thing { A } impl Thing { pub fn new() -> Self<|> { Thing::A } } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); let (analysis, position) = analysis_and_position( " enum Thing { A } impl Thing { pub fn thing(a: Self<|>) { } } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); */ } #[test] fn test_hover_shadowing_pat() { let (analysis, position) = analysis_and_position( " fn x() {} fn y() { let x = 0i32; x<|>; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); } #[test] fn test_hover_macro_invocation() { let (analysis, position) = analysis_and_position( " macro_rules! foo { () => {} } fn f() { fo<|>o!(); } ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); } #[test] fn test_hover_tuple_field() { let (analysis, position) = analysis_and_position( " struct TS(String, i32<|>); ", ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); } #[test] fn test_hover_through_macro() { let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id { ($($tt:tt)*) => { $($tt)* } } fn foo() {} id! { fn bar() { fo<|>o(); } } ", &["fn foo()"], ); assert_eq!(hover_on, "foo") } #[test] fn test_hover_through_expr_in_macro() { let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id { ($($tt:tt)*) => { $($tt)* } } fn foo(bar:u32) { let a = id!(ba<|>r); } ", &["u32"], ); assert_eq!(hover_on, "bar") } #[test] fn test_hover_through_expr_in_macro_recursive() { let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } } macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } } fn foo(bar:u32) { let a = id!(ba<|>r); } ", &["u32"], ); assert_eq!(hover_on, "bar") } #[test] fn test_hover_through_func_in_macro_recursive() { let (hover_on, _) = check_hover_result( " //- /lib.rs 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(<|>)] ); } ", &["u32"], ); assert_eq!(hover_on, "bar()") } #[test] fn test_hover_through_literal_string_in_macro() { let (hover_on, _) = check_hover_result( r#" //- /lib.rs macro_rules! arr { ($($tt:tt)*) => { [$($tt)*)] } } fn foo() { let mastered_for_itunes = ""; let _ = arr!("Tr<|>acks", &mastered_for_itunes); } "#, &["&str"], ); assert_eq!(hover_on, "\"Tracks\""); } #[test] fn test_hover_through_assert_macro() { let (hover_on, _) = check_hover_result( r#" //- /lib.rs #[rustc_builtin_macro] macro_rules! assert {} fn bar() -> bool { true } fn foo() { assert!(ba<|>r()); } "#, &["fn bar() -> bool"], ); assert_eq!(hover_on, "bar"); } #[test] fn test_hover_through_literal_string_in_builtin_macro() { check_hover_no_result( r#" //- /lib.rs #[rustc_builtin_macro] macro_rules! format {} fn foo() { format!("hel<|>lo {}", 0); } "#, ); } #[test] fn test_hover_non_ascii_space_doc() { check_hover_result( " //- /lib.rs /// <- `\u{3000}` here fn foo() { } fn bar() { fo<|>o(); } ", &["fn foo()\n```\n___\n\n<- `\u{3000}` here"], ); } #[test] fn test_hover_function_show_qualifiers() { check_hover_result( " //- /lib.rs async fn foo<|>() {} ", &["async fn foo()"], ); check_hover_result( " //- /lib.rs pub const unsafe fn foo<|>() {} ", &["pub const unsafe fn foo()"], ); check_hover_result( r#" //- /lib.rs pub(crate) async unsafe extern "C" fn foo<|>() {} "#, &[r#"pub(crate) async unsafe extern "C" fn foo()"#], ); } #[test] fn test_hover_trait_show_qualifiers() { let (_, actions) = check_hover_result( " //- /lib.rs unsafe trait foo<|>() {} ", &["unsafe trait foo"], ); assert_impl_action(&actions[0], 13); } #[test] fn test_hover_mod_with_same_name_as_function() { check_hover_result( " //- /lib.rs use self::m<|>y::Bar; mod my { pub struct Bar; } fn my() {} ", &["mod my"], ); } #[test] fn test_hover_struct_doc_comment() { check_hover_result( r#" //- /lib.rs /// bar docs struct Bar; fn foo() { let bar = Ba<|>r; } "#, &["struct Bar\n```\n___\n\nbar docs"], ); } #[test] fn test_hover_struct_doc_attr() { check_hover_result( r#" //- /lib.rs #[doc = "bar docs"] struct Bar; fn foo() { let bar = Ba<|>r; } "#, &["struct Bar\n```\n___\n\nbar docs"], ); } #[test] fn test_hover_struct_doc_attr_multiple_and_mixed() { check_hover_result( r#" //- /lib.rs /// bar docs 0 #[doc = "bar docs 1"] #[doc = "bar docs 2"] struct Bar; fn foo() { let bar = Ba<|>r; } "#, &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], ); } #[test] fn test_hover_macro_generated_struct_fn_doc_comment() { mark::check!(hover_macro_generated_struct_fn_doc_comment); check_hover_result( r#" //- /lib.rs macro_rules! bar { () => { struct Bar; impl Bar { /// Do the foo fn foo(&self) {} } } } bar!(); fn foo() { let bar = Bar; bar.fo<|>o(); } "#, &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], ); } #[test] fn test_hover_macro_generated_struct_fn_doc_attr() { mark::check!(hover_macro_generated_struct_fn_doc_attr); check_hover_result( r#" //- /lib.rs macro_rules! bar { () => { struct Bar; impl Bar { #[doc = "Do the foo"] fn foo(&self) {} } } } bar!(); fn foo() { let bar = Bar; bar.fo<|>o(); } "#, &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], ); } #[test] fn test_hover_trait_has_impl_action() { let (_, actions) = check_hover_result( " //- /lib.rs trait foo<|>() {} ", &["trait foo"], ); assert_impl_action(&actions[0], 6); } #[test] fn test_hover_struct_has_impl_action() { let (_, actions) = check_hover_result( " //- /lib.rs struct foo<|>() {} ", &["struct foo"], ); assert_impl_action(&actions[0], 7); } #[test] fn test_hover_union_has_impl_action() { let (_, actions) = check_hover_result( " //- /lib.rs union foo<|>() {} ", &["union foo"], ); assert_impl_action(&actions[0], 6); } #[test] fn test_hover_enum_has_impl_action() { let (_, actions) = check_hover_result( " //- /lib.rs enum foo<|>() { A, B } ", &["enum foo"], ); assert_impl_action(&actions[0], 5); } #[test] fn test_hover_test_has_action() { let (_, actions) = check_hover_result( " //- /lib.rs #[test] fn foo_<|>test() {} ", &["fn foo_test()"], ); assert_debug_snapshot!(actions, @r###" [ Runnable( Runnable { nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..24, name: "foo_test", kind: FN_DEF, focus_range: Some( 11..19, ), container_name: None, description: None, docs: None, }, kind: Test { test_id: Path( "foo_test", ), attr: TestAttr { ignore: false, }, }, cfg_exprs: [], }, ), ] "###); } #[test] fn test_hover_test_mod_has_action() { let (_, actions) = check_hover_result( " //- /lib.rs mod tests<|> { #[test] fn foo_test() {} } ", &["mod tests"], ); assert_debug_snapshot!(actions, @r###" [ Runnable( Runnable { nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..46, name: "tests", kind: MODULE, focus_range: Some( 4..9, ), container_name: None, description: None, docs: None, }, kind: TestMod { path: "tests", }, cfg_exprs: [], }, ), ] "###); } #[test] fn test_hover_struct_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs struct S{ f1: u32 } fn main() { let s<|>t = S{ f1:0 }; } ", &["S"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..19, name: "S", kind: STRUCT_DEF, focus_range: Some( 7..8, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_generic_struct_has_goto_type_actions() { let (_, actions) = check_hover_result( " //- /main.rs struct Arg(u32); struct S{ f1: T } fn main() { let s<|>t = S{ f1:Arg(0) }; } ", &["S"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 17..37, name: "S", kind: STRUCT_DEF, focus_range: Some( 24..25, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, HoverGotoTypeData { mod_path: "Arg", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..16, name: "Arg", kind: STRUCT_DEF, focus_range: Some( 7..10, ), container_name: None, description: Some( "struct Arg", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_generic_struct_has_flattened_goto_type_actions() { let (_, actions) = check_hover_result( " //- /main.rs struct Arg(u32); struct S{ f1: T } fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; } ", &["S>"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 17..37, name: "S", kind: STRUCT_DEF, focus_range: Some( 24..25, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, HoverGotoTypeData { mod_path: "Arg", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..16, name: "Arg", kind: STRUCT_DEF, focus_range: Some( 7..10, ), container_name: None, description: Some( "struct Arg", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_tuple_has_goto_type_actions() { let (_, actions) = check_hover_result( " //- /main.rs struct A(u32); struct B(u32); mod M { pub struct C(u32); } fn main() { let s<|>t = (A(1), B(2), M::C(3) ); } ", &["(A, B, C)"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "A", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..14, name: "A", kind: STRUCT_DEF, focus_range: Some( 7..8, ), container_name: None, description: Some( "struct A", ), docs: None, }, }, HoverGotoTypeData { mod_path: "B", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 15..29, name: "B", kind: STRUCT_DEF, focus_range: Some( 22..23, ), container_name: None, description: Some( "struct B", ), docs: None, }, }, HoverGotoTypeData { mod_path: "M::C", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 42..60, name: "C", kind: STRUCT_DEF, focus_range: Some( 53..54, ), container_name: None, description: Some( "pub struct C", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_return_impl_trait_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs trait Foo {} fn foo() -> impl Foo {} fn main() { let s<|>t = foo(); } ", &["impl Foo"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..12, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_generic_return_impl_trait_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs trait Foo {} struct S; fn foo() -> impl Foo {} fn main() { let s<|>t = foo(); } ", &["impl Foo"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..15, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 16..25, name: "S", kind: STRUCT_DEF, focus_range: Some( 23..24, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_return_impl_traits_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs trait Foo {} trait Bar {} fn foo() -> impl Foo + Bar {} fn main() { let s<|>t = foo(); } ", &["impl Foo + Bar"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..12, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, HoverGotoTypeData { mod_path: "Bar", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 13..25, name: "Bar", kind: TRAIT_DEF, focus_range: Some( 19..22, ), container_name: None, description: Some( "trait Bar", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_generic_return_impl_traits_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs trait Foo {} trait Bar {} struct S1 {} struct S2 {} fn foo() -> impl Foo + Bar {} fn main() { let s<|>t = foo(); } ", &["impl Foo + Bar"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..15, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, HoverGotoTypeData { mod_path: "Bar", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 16..31, name: "Bar", kind: TRAIT_DEF, focus_range: Some( 22..25, ), container_name: None, description: Some( "trait Bar", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S1", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 32..44, name: "S1", kind: STRUCT_DEF, focus_range: Some( 39..41, ), container_name: None, description: Some( "struct S1", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S2", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 45..57, name: "S2", kind: STRUCT_DEF, focus_range: Some( 52..54, ), container_name: None, description: Some( "struct S2", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_arg_impl_trait_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /lib.rs trait Foo {} fn foo(ar<|>g: &impl Foo) {} ", &["&impl Foo"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..12, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_arg_impl_traits_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /lib.rs trait Foo {} trait Bar {} struct S{} fn foo(ar<|>g: &impl Foo + Bar) {} ", &["&impl Foo + Bar"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..12, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, HoverGotoTypeData { mod_path: "Bar", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 13..28, name: "Bar", kind: TRAIT_DEF, focus_range: Some( 19..22, ), container_name: None, description: Some( "trait Bar", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 29..39, name: "S", kind: STRUCT_DEF, focus_range: Some( 36..37, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_arg_generic_impl_trait_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /lib.rs trait Foo {} struct S {} fn foo(ar<|>g: &impl Foo) {} ", &["&impl Foo"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..15, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 16..27, name: "S", kind: STRUCT_DEF, focus_range: Some( 23..24, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_dyn_return_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs trait Foo {} struct S; impl Foo for S {} struct B{} fn foo() -> B {} fn main() { let s<|>t = foo(); } ", &["B"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "B", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 42..55, name: "B", kind: STRUCT_DEF, focus_range: Some( 49..50, ), container_name: None, description: Some( "struct B", ), docs: None, }, }, HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..12, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_dyn_arg_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /lib.rs trait Foo {} fn foo(ar<|>g: &dyn Foo) {} ", &["&dyn Foo"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..12, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_generic_dyn_arg_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /lib.rs trait Foo {} struct S {} fn foo(ar<|>g: &dyn Foo) {} ", &["&dyn Foo"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..15, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 16..27, name: "S", kind: STRUCT_DEF, focus_range: Some( 23..24, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_goto_type_action_links_order() { let (_, actions) = check_hover_result( " //- /lib.rs trait ImplTrait {} trait DynTrait {} struct B {} struct S {} fn foo(a<|>rg: &impl ImplTrait>>>) {} ", &["&impl ImplTrait>>>"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "ImplTrait", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..21, name: "ImplTrait", kind: TRAIT_DEF, focus_range: Some( 6..15, ), container_name: None, description: Some( "trait ImplTrait", ), docs: None, }, }, HoverGotoTypeData { mod_path: "B", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 43..57, name: "B", kind: STRUCT_DEF, focus_range: Some( 50..51, ), container_name: None, description: Some( "struct B", ), docs: None, }, }, HoverGotoTypeData { mod_path: "DynTrait", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 22..42, name: "DynTrait", kind: TRAIT_DEF, focus_range: Some( 28..36, ), container_name: None, description: Some( "trait DynTrait", ), docs: None, }, }, HoverGotoTypeData { mod_path: "S", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 58..69, name: "S", kind: STRUCT_DEF, focus_range: Some( 65..66, ), container_name: None, description: Some( "struct S", ), docs: None, }, }, ], ), ] "###); } #[test] fn test_hover_associated_type_has_goto_type_action() { let (_, actions) = check_hover_result( " //- /main.rs 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(); } ", &["Foo::Item"], ); assert_debug_snapshot!(actions, @r###" [ GoToType( [ HoverGotoTypeData { mod_path: "Foo", nav: NavigationTarget { file_id: FileId( 1, ), full_range: 0..62, name: "Foo", kind: TRAIT_DEF, focus_range: Some( 6..9, ), container_name: None, description: Some( "trait Foo", ), docs: None, }, }, ], ), ] "###); } }