9761: feat: Show coerced types on type hover r=Veykril a=Veykril

This applies to both the ranged hover request as well as the normal hover type fallback.
![image](https://user-images.githubusercontent.com/3757771/127883884-2935b624-a3e5-4f35-861a-7d6d3266d187.png)
![image](https://user-images.githubusercontent.com/3757771/127883951-4ff96b6b-7576-4886-887b-1198c1121841.png)

We unfortunately have to leave out syntax highlighting here as otherwise the `Type` and `Coerced` words in the hover will get colored.

Note that this does not show all the coercions yet(and almost no pattern coercions) as not all coercion adjustments are implemented yet.

Closes https://github.com/rust-analyzer/rust-analyzer/issues/2677

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2021-08-02 15:44:43 +00:00 committed by GitHub
commit cae54d86d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 157 additions and 79 deletions

View file

@ -225,7 +225,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.type_of_pat(pat)
}
pub fn type_of_pat_with_coercion(&self, expr: &ast::Pat) -> Option<Type> {
pub fn type_of_pat_with_coercion(&self, expr: &ast::Pat) -> Option<(Type, bool)> {
self.imp.type_of_pat_with_coercion(expr)
}
@ -577,7 +577,7 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(pat.syntax()).type_of_pat(self.db, pat)
}
fn type_of_pat_with_coercion(&self, pat: &ast::Pat) -> Option<Type> {
fn type_of_pat_with_coercion(&self, pat: &ast::Pat) -> Option<(Type, bool)> {
self.analyze(pat.syntax()).type_of_pat_with_coercion(self.db, pat)
}

View file

@ -147,15 +147,15 @@ impl SourceAnalyzer {
&self,
db: &dyn HirDatabase,
pat: &ast::Pat,
) -> Option<Type> {
) -> Option<(Type, bool)> {
let pat_id = self.pat_id(pat)?;
let infer = self.infer.as_ref()?;
let ty = infer
let (ty, coerced) = infer
.pat_adjustments
.get(&pat_id)
.and_then(|adjusts| adjusts.last().map(|adjust| &adjust.target))
.unwrap_or_else(|| &infer[pat_id]);
Type::new_with_resolver(db, &self.resolver, ty.clone())
.and_then(|adjusts| adjusts.last().map(|adjust| (&adjust.target, true)))
.unwrap_or_else(|| (&infer[pat_id], false));
Type::new_with_resolver(db, &self.resolver, ty.clone()).zip(Some(coerced))
}
pub(crate) fn type_of_self(

View file

@ -12,12 +12,8 @@ use ide_db::{
use itertools::Itertools;
use stdx::format_to;
use syntax::{
algo::{self, find_node_at_range},
ast,
display::fn_as_proc_macro_label,
match_ast, AstNode, AstToken, Direction,
SyntaxKind::*,
SyntaxToken, T,
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
SyntaxKind::*, SyntaxToken, T,
};
use crate::{
@ -79,34 +75,28 @@ pub struct HoverResult {
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
db: &RootDatabase,
range: FileRange,
FileRange { file_id, range }: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
let sema = hir::Semantics::new(db);
let file = sema.parse(range.file_id).syntax().clone();
let file = sema.parse(file_id).syntax().clone();
// This means we're hovering over a range.
if !range.range.is_empty() {
let expr = find_node_at_range::<ast::Expr>(&file, range.range)?;
let ty = sema.type_of_expr(&expr)?;
if ty.is_unknown() {
return None;
}
let mut res = HoverResult::default();
res.markup = if config.markdown() {
Markup::fenced_block(&ty.display(db))
let offset = if range.is_empty() {
range.start()
} else {
ty.display(db).to_string().into()
let expr = file.covering_element(range).ancestors().find_map(|it| {
match_ast! {
match it {
ast::Expr(expr) => Some(Either::Left(expr)),
ast::Pat(pat) => Some(Either::Right(pat)),
_ => None,
}
}
})?;
return hover_type_info(&sema, config, expr).map(|it| RangeInfo::new(range, it));
};
return Some(RangeInfo::new(range.range, res));
}
let position = FilePosition { file_id: range.file_id, offset: range.range.start() };
let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
let token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
@ -114,8 +104,6 @@ pub(crate) fn hover(
})?;
let token = sema.descend_into_macros(token);
let mut res = HoverResult::default();
let node = token.parent()?;
let mut range = None;
let definition = match_ast! {
@ -146,8 +134,8 @@ pub(crate) fn hover(
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
let (idl_range, link, ns) =
extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
let hir::InFile { file_id, value: mapped_range } = doc_mapping.map(range)?;
(file_id == position.file_id.into() && mapped_range.contains(position.offset)).then(||(mapped_range, link, ns))
let mapped = doc_mapping.map(range)?;
(mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
})?;
range = Some(idl_range);
Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
@ -176,6 +164,7 @@ pub(crate) fn hover(
_ => None,
};
if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) {
let mut res = HoverResult::default();
res.markup = process_markup(sema.db, definition, &markup, config);
if let Some(action) = show_implementations_action(db, definition) {
res.actions.push(action);
@ -185,7 +174,7 @@ pub(crate) fn hover(
res.actions.push(action);
}
if let Some(action) = runnable_action(&sema, definition, position.file_id) {
if let Some(action) = runnable_action(&sema, definition, file_id) {
res.actions.push(action);
}
@ -207,10 +196,10 @@ pub(crate) fn hover(
.take_while(|it| !ast::Item::can_cast(it.kind()))
.find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.kind()))?;
let ty = match_ast! {
let expr_or_pat = match_ast! {
match node {
ast::Expr(it) => sema.type_of_expr(&it)?,
ast::Pat(it) => sema.type_of_pat(&it)?,
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` failed to resolve.
// (e.g expanding a builtin macro). So we give up here.
ast::MacroCall(_it) => return None,
@ -218,16 +207,48 @@ pub(crate) fn hover(
}
};
res.markup = if config.markdown() {
Markup::fenced_block(&ty.display(db))
} else {
ty.display(db).to_string().into()
};
let res = hover_type_info(&sema, config, expr_or_pat)?;
let range = sema.original_range(&node).range;
Some(RangeInfo::new(range, res))
}
fn hover_type_info(
sema: &Semantics<RootDatabase>,
config: &HoverConfig,
expr_or_pat: Either<ast::Expr, ast::Pat>,
) -> Option<HoverResult> {
let (ty, coerced) = match &expr_or_pat {
Either::Left(expr) => sema.type_of_expr_with_coercion(expr)?,
Either::Right(pat) => sema.type_of_pat_with_coercion(pat)?,
};
let mut res = HoverResult::default();
res.markup = if coerced {
let uncoerced_ty = match &expr_or_pat {
Either::Left(expr) => sema.type_of_expr(expr)?,
Either::Right(pat) => sema.type_of_pat(pat)?,
};
let uncoerced = uncoerced_ty.display(sema.db).to_string();
let coerced = ty.display(sema.db).to_string();
format!(
"```text\nType: {:>upad$}\nCoerced to: {:>cpad$}\n```\n",
uncoerced = uncoerced,
coerced = coerced,
// 6 base padding for static text prefix of each line
upad = 6 + coerced.len().max(uncoerced.len()),
cpad = uncoerced.len(),
)
.into()
} else {
if config.markdown() {
Markup::fenced_block(&ty.display(sema.db))
} else {
ty.display(sema.db).to_string().into()
}
};
Some(res)
}
fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
let (path, tt) = attr.as_simple_call()?;
if !tt.syntax().text_range().contains(token.text_range().start()) {
@ -4081,4 +4102,61 @@ fn f() { let expr$0 = $0[1, 2, 3, 4] }
```"#]],
);
}
#[test]
fn hover_range_for_pat() {
check_hover_range(
r#"
fn foo() {
let $0x$0 = 0;
}
"#,
expect![[r#"
```rust
i32
```"#]],
);
check_hover_range(
r#"
fn foo() {
let $0x$0 = "";
}
"#,
expect![[r#"
```rust
&str
```"#]],
);
}
#[test]
fn hover_range_shows_coercions_if_applicable_expr() {
check_hover_range(
r#"
fn foo() {
let x: &u32 = $0&&&&&0$0;
}
"#,
expect![[r#"
```text
Type: &&&&&u32
Coerced to: &u32
```
"#]],
);
check_hover_range(
r#"
fn foo() {
let x: *const u32 = $0&0$0;
}
"#,
expect![[r#"
```text
Type: &u32
Coerced to: *const u32
```
"#]],
);
}
}