Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #13989 - Veykril:hover, r=Veykril
internal: Remove hover fallback in favor of ranged hover The fallback is usually more annoying than useful at this point (it messes with the range of diagnostic popups a lot), we now have a ranged hover to check the type of something which works a lot better. Closes https://github.com/rust-lang/rust-analyzer/issues/11602
bors 2023-01-21
parent ce67dea · parent 4685b97 · commit 303737d
-rw-r--r--crates/ide/src/hover.rs150
-rw-r--r--crates/ide/src/hover/render.rs108
-rw-r--r--crates/ide/src/hover/tests.rs166
3 files changed, 235 insertions, 189 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index c46c1c1cd1..2058a4f5f1 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -15,7 +15,7 @@ use ide_db::{
FxIndexSet, RootDatabase,
};
use itertools::Itertools;
-use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};
+use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
use crate::{
doc_links::token_as_doc_comment,
@@ -86,30 +86,38 @@ pub struct HoverResult {
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
db: &RootDatabase,
- file_range: FileRange,
+ frange @ FileRange { file_id, range }: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
let sema = &hir::Semantics::new(db);
- let mut res = hover_impl(sema, file_range, config)?;
+ let file = sema.parse(file_id).syntax().clone();
+ let mut res = if range.is_empty() {
+ hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config)
+ } else {
+ hover_ranged(sema, frange, file, config)
+ }?;
+
if let HoverDocFormat::PlainText = config.format {
res.info.markup = remove_markdown(res.info.markup.as_str()).into();
}
Some(res)
}
-fn hover_impl(
+fn hover_simple(
sema: &Semantics<'_, RootDatabase>,
- FileRange { file_id, range }: FileRange,
+ FilePosition { file_id, offset }: FilePosition,
+ file: SyntaxNode,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
- let file = sema.parse(file_id).syntax().clone();
- if !range.is_empty() {
- return hover_ranged(&file, range, sema, config);
- }
- let offset = range.start();
-
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
- IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 4,
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | T![_] => 4,
// index and prefix ops
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
kind if kind.is_keyword() => 2,
@@ -142,19 +150,18 @@ fn hover_impl(
} else {
sema.descend_into_macros_with_same_text(original_token.clone())
};
+ let descended = || descended.iter();
- // try lint hover
- let result = descended
- .iter()
+ let result = descended()
+ // try lint hover
.find_map(|token| {
// FIXME: Definition should include known lints and the like instead of having this special case here
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
render::try_for_lint(&attr, token)
})
- // try item definitions
+ // try definitions
.or_else(|| {
- descended
- .iter()
+ descended()
.filter_map(|token| {
let node = token.parent()?;
let class = IdentClass::classify_token(sema, token)?;
@@ -175,10 +182,12 @@ fn hover_impl(
})
})
// try keywords
- .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
- // try rest item hover
+ .or_else(|| descended().find_map(|token| render::keyword(sema, config, token)))
+ // try _ hovers
+ .or_else(|| descended().find_map(|token| render::underscore(sema, config, token)))
+ // try rest pattern hover
.or_else(|| {
- descended.iter().find_map(|token| {
+ descended().find_map(|token| {
if token.kind() != DOT2 {
return None;
}
@@ -194,51 +203,24 @@ fn hover_impl(
})
});
- result
- .map(|mut res: HoverResult| {
- res.actions = dedupe_or_merge_hover_actions(res.actions);
- RangeInfo::new(original_token.text_range(), res)
- })
- // fallback to type hover if there aren't any other suggestions
- // this finds its own range instead of using the closest token's range
- .or_else(|| {
- descended.iter().find_map(|token| hover_type_fallback(sema, config, token, token))
- })
-}
-
-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::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
- _ => None,
- };
- render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
- HoverResult {
- markup: render::process_markup(sema.db, definition, &markup, config),
- actions: show_implementations_action(sema.db, definition)
- .into_iter()
- .chain(show_fn_references_action(sema.db, definition))
- .chain(runnable_action(sema, definition, file_id))
- .chain(goto_type_action_for_def(sema.db, definition))
- .collect(),
- }
+ result.map(|mut res: HoverResult| {
+ res.actions = dedupe_or_merge_hover_actions(res.actions);
+ RangeInfo::new(original_token.text_range(), res)
})
}
fn hover_ranged(
- file: &SyntaxNode,
- range: syntax::TextRange,
sema: &Semantics<'_, RootDatabase>,
+ FileRange { range, .. }: FileRange,
+ file: SyntaxNode,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
// FIXME: make this work in attributes
- let expr_or_pat =
- file.covering_element(range).ancestors().find_map(Either::<ast::Expr, ast::Pat>::cast)?;
+ let expr_or_pat = file
+ .covering_element(range)
+ .ancestors()
+ .take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind()))
+ .find_map(Either::<ast::Expr, ast::Pat>::cast)?;
let res = match &expr_or_pat {
Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
Either::Left(ast::Expr::PrefixExpr(prefix_expr))
@@ -248,7 +230,7 @@ fn hover_ranged(
}
_ => None,
};
- let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
+ let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat));
res.map(|it| {
let range = match expr_or_pat {
Either::Left(it) => it.syntax().text_range(),
@@ -258,37 +240,31 @@ fn hover_ranged(
})
}
-fn hover_type_fallback(
+pub(crate) fn hover_for_definition(
sema: &Semantics<'_, RootDatabase>,
+ file_id: FileId,
+ definition: Definition,
+ node: &SyntaxNode,
config: &HoverConfig,
- token: &SyntaxToken,
- original_token: &SyntaxToken,
-) -> Option<RangeInfo<HoverResult>> {
- let node =
- token.parent_ancestors().take_while(|it| !ast::Item::can_cast(it.kind())).find(|n| {
- ast::Expr::can_cast(n.kind())
- || ast::Pat::can_cast(n.kind())
- || ast::Type::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,
- }
+) -> Option<HoverResult> {
+ let famous_defs = match &definition {
+ Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
+ _ => None,
};
-
- let res = render::type_info(sema, config, &expr_or_pat)?;
-
- let range = sema
- .original_range_opt(&node)
- .map(|frange| frange.range)
- .unwrap_or_else(|| original_token.text_range());
- Some(RangeInfo::new(range, res))
+ render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
+ HoverResult {
+ markup: render::process_markup(sema.db, definition, &markup, config),
+ actions: [
+ show_implementations_action(sema.db, definition),
+ show_fn_references_action(sema.db, definition),
+ runnable_action(sema, definition, file_id),
+ goto_type_action_for_def(sema.db, definition),
+ ]
+ .into_iter()
+ .flatten()
+ .collect(),
+ }
+ })
}
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index d7b6264957..22611cfb89 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -29,7 +29,7 @@ use crate::{
HoverAction, HoverConfig, HoverResult, Markup,
};
-pub(super) fn type_info(
+pub(super) fn type_info_of(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,
expr_or_pat: &Either<ast::Expr, ast::Pat>,
@@ -38,34 +38,7 @@ pub(super) fn type_info(
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!(
- "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
- original,
- adjusted,
- apad = static_text_diff_len + adjusted.len().max(original.len()),
- opad = original.len(),
- )
- .into()
- } else {
- Markup::fenced_block(&original.display(sema.db))
- };
- res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
- Some(res)
+ type_info(sema, _config, original, adjusted)
}
pub(super) fn try_expr(
@@ -217,6 +190,48 @@ pub(super) fn deref_expr(
Some(res)
}
+pub(super) fn underscore(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+) -> Option<HoverResult> {
+ if token.kind() != T![_] {
+ return None;
+ }
+ let parent = token.parent()?;
+ let _it = match_ast! {
+ match parent {
+ ast::InferType(it) => it,
+ ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))),
+ ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))),
+ _ => return None,
+ }
+ };
+ // let it = infer_type.syntax().parent()?;
+ // match_ast! {
+ // match it {
+ // ast::LetStmt(_it) => (),
+ // ast::Param(_it) => (),
+ // ast::RetType(_it) => (),
+ // ast::TypeArg(_it) => (),
+
+ // ast::CastExpr(_it) => (),
+ // ast::ParenType(_it) => (),
+ // ast::TupleType(_it) => (),
+ // ast::PtrType(_it) => (),
+ // ast::RefType(_it) => (),
+ // ast::ArrayType(_it) => (),
+ // ast::SliceType(_it) => (),
+ // ast::ForType(_it) => (),
+ // _ => return None,
+ // }
+ // }
+
+ // FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown
+ // type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None)
+ None
+}
+
pub(super) fn keyword(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
@@ -458,6 +473,41 @@ pub(super) fn definition(
markup(docs, label, mod_path)
}
+fn type_info(
+ sema: &Semantics<'_, RootDatabase>,
+ _config: &HoverConfig,
+ original: hir::Type,
+ adjusted: Option<hir::Type>,
+) -> Option<HoverResult> {
+ 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!(
+ "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
+ original,
+ adjusted,
+ apad = static_text_diff_len + adjusted.len().max(original.len()),
+ opad = original.len(),
+ )
+ .into()
+ } else {
+ Markup::fenced_block(&original.display(sema.db))
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ Some(res)
+}
+
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
let name = attr.name(db);
let desc = format!("#[{name}]");
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index db2aaddc0b..2830212add 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -214,37 +214,20 @@ m!(ab$0c);
}
#[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;
+ let foo_test = foo$0();
}
"#,
expect![[r#"
- *foo()*
- u32
+ *foo*
+ test
+
+ pub fn foo() -> u32
"#]],
);
}
@@ -304,33 +287,6 @@ fn main() { let foo_test = fo$0o(); }
"#]],
);
- // 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#"
@@ -1194,33 +1150,19 @@ fn test_hover_through_func_in_macro_recursive() {
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)] ); }
+fn foo() { let a = id!([0u32, bar$0()] ); }
"#,
expect![[r#"
- *bar()*
- ```rust
- u32
- ```
- "#]],
- );
-}
+ *bar*
-#[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
- ```
- "#]],
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar() -> u32
+ ```
+ "#]],
);
}
@@ -5592,3 +5534,81 @@ fn main() {
"#]],
);
}
+
+#[test]
+fn hover_underscore_pat() {
+ check(
+ r#"
+fn main() {
+ let _$0 = 0;
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn main() {
+ let (_$0,) = (0,);
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_underscore_expr() {
+ check(
+ r#"
+fn main() {
+ _$0 = 0;
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn main() {
+ (_$0,) = (0,);
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_underscore_type() {
+ check_hover_no_result(
+ r#"
+fn main() {
+ let x: _$0 = 0;
+}
+"#,
+ );
+ check_hover_no_result(
+ r#"
+fn main() {
+ let x: (_$0,) = (0,);
+}
+"#,
+ );
+}