From a542bd46bf22a56dd7b519de515949fef6976b42 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 20 Jan 2023 16:30:08 +0100 Subject: [PATCH] Split out some hover functions --- crates/ide/src/hover.rs | 126 ++++++++++++++++++--------------- crates/ide/src/hover/render.rs | 108 ++++++++++++++++++++-------- crates/ide/src/hover/tests.rs | 90 +++++++++++++++++++++++ 3 files changed, 237 insertions(+), 87 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index c46c1c1cd1e..4a76ac9320d 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -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> { 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> { - 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; } @@ -201,9 +210,35 @@ fn hover_impl( }) // 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)) - }) + .or_else(|| descended().find_map(|token| hover_type_fallback(sema, config, token, token))) +} + +fn hover_ranged( + sema: &Semantics<'_, RootDatabase>, + FileRange { range, .. }: FileRange, + file: SyntaxNode, + config: &HoverConfig, +) -> Option> { + // FIXME: make this work in attributes + let expr_or_pat = + file.covering_element(range).ancestors().find_map(Either::::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)) + if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) => + { + render::deref_expr(sema, config, prefix_expr) + } + _ => None, + }; + 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(), + Either::Right(it) => it.syntax().text_range(), + }; + RangeInfo::new(range, it) + }) } pub(crate) fn hover_for_definition( @@ -220,44 +255,19 @@ pub(crate) fn hover_for_definition( 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(), + 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 hover_ranged( - file: &SyntaxNode, - range: syntax::TextRange, - sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, -) -> Option> { - // FIXME: make this work in attributes - let expr_or_pat = - file.covering_element(range).ancestors().find_map(Either::::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)) - if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) => - { - render::deref_expr(sema, config, prefix_expr) - } - _ => None, - }; - let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat)); - res.map(|it| { - let range = match expr_or_pat { - Either::Left(it) => it.syntax().text_range(), - Either::Right(it) => it.syntax().text_range(), - }; - RangeInfo::new(range, it) - }) -} - fn hover_type_fallback( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, @@ -282,7 +292,7 @@ fn hover_type_fallback( } }; - let res = render::type_info(sema, config, &expr_or_pat)?; + let res = render::type_info_of(sema, config, &expr_or_pat)?; let range = sema .original_range_opt(&node) diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index d7b62649578..22611cfb892 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -29,7 +29,7 @@ HoverAction, HoverConfig, HoverResult, Markup, }; -pub(super) fn type_info( +pub(super) fn type_info_of( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, expr_or_pat: &Either, @@ -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 = 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 { + 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, +) -> Option { + let mut res = HoverResult::default(); + let mut targets: Vec = 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 { 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 db2aaddc0be..2930aab68a9 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -5592,3 +5592,93 @@ 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( + r#" +fn main() { + let x: _$0 = 0; +} +"#, + expect![[r#" + *_* + ```rust + {unknown} + ``` + "#]], + ); + check( + r#" +fn main() { + let x: (_$0,) = (0,); +} +"#, + expect![[r#" + *_* + ```rust + {unknown} + ``` + "#]], + ); +}