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)?;
let offset = if range.is_empty() {
range.start()
} else {
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));
};
if ty.is_unknown() {
return None;
}
let mut res = HoverResult::default();
res.markup = if config.markdown() {
Markup::fenced_block(&ty.display(db))
} else {
ty.display(db).to_string().into()
};
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()) {
@ -1192,7 +1213,7 @@ impl Thing {
}
fn main() { let foo_$0test = Thing::new(); }
"#,
"#,
expect![[r#"
*foo_test*
@ -1562,7 +1583,7 @@ fn foo() {
fn foo() {
format!("hel$0lo {}", 0);
}
"#,
"#,
);
}
@ -1670,7 +1691,7 @@ extern crate st$0d;
//!
//! Printed?
//! abc123
"#,
"#,
expect![[r#"
*std*
@ -1695,7 +1716,7 @@ extern crate std as ab$0c;
//!
//! Printed?
//! abc123
"#,
"#,
expect![[r#"
*abc*
@ -2214,7 +2235,7 @@ mod tests$0 {
struct S{ f1: u32 }
fn main() { let s$0t = S{ f1:0 }; }
"#,
"#,
expect![[r#"
[
GoToType(
@ -2293,7 +2314,7 @@ struct Arg(u32);
struct S<T>{ f1: T }
fn main() { let s$0t = S{ f1: S{ f1: Arg(0) } }; }
"#,
"#,
expect![[r#"
[
GoToType(
@ -2482,7 +2503,7 @@ trait Bar {}
fn foo() -> impl Foo + Bar {}
fn main() { let s$0t = foo(); }
"#,
"#,
expect![[r#"
[
GoToType(
@ -2915,7 +2936,7 @@ struct B<T> {}
struct S {}
fn foo(a$0rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
"#,
"#,
expect![[r#"
[
GoToType(
@ -3711,7 +3732,7 @@ mod string {
/// This is `alloc::String`.
pub struct String;
}
"#,
"#,
expect![[r#"
*String*
@ -3830,7 +3851,7 @@ pub fn foo() {}
//- /lib.rs crate:main.rs deps:foo
#[fo$0o::bar()]
struct Foo;
"#,
"#,
expect![[r#"
*foo*
@ -3846,7 +3867,7 @@ struct Foo;
check(
r#"
use self as foo$0;
"#,
"#,
expect![[r#"
*foo*
@ -3859,7 +3880,7 @@ use self as foo$0;
r#"
mod bar {}
use bar::{self as foo$0};
"#,
"#,
expect![[r#"
*foo*
@ -3877,7 +3898,7 @@ use bar::{self as foo$0};
mod bar {
use super as foo$0;
}
"#,
"#,
expect![[r#"
*foo*
@ -3889,7 +3910,7 @@ mod bar {
check(
r#"
use crate as foo$0;
"#,
"#,
expect![[r#"
*foo*
@ -3908,7 +3929,7 @@ use crate as foo$0;
pub macro Copy {}
#[derive(Copy$0)]
struct Foo;
"#,
"#,
expect![[r#"
*Copy*
@ -3929,7 +3950,7 @@ mod foo {
}
#[derive(foo::Copy$0)]
struct Foo;
"#,
"#,
expect![[r#"
*Copy*
@ -3949,7 +3970,7 @@ struct Foo;
check_hover_range(
r#"
fn f() { let expr = $01 + 2 * 3$0 }
"#,
"#,
expect![[r#"
```rust
i32
@ -3959,7 +3980,7 @@ fn f() { let expr = $01 + 2 * 3$0 }
check_hover_range(
r#"
fn f() { let expr = 1 $0+ 2 * $03 }
"#,
"#,
expect![[r#"
```rust
i32
@ -3969,7 +3990,7 @@ fn f() { let expr = 1 $0+ 2 * $03 }
check_hover_range(
r#"
fn f() { let expr = 1 + $02 * 3$0 }
"#,
"#,
expect![[r#"
```rust
i32
@ -3982,7 +4003,7 @@ fn f() { let expr = 1 + $02 * 3$0 }
check_hover_range(
r#"
fn f() { let expr = $0[1, 2, 3, 4]$0 }
"#,
"#,
expect![[r#"
```rust
[i32; 4]
@ -3992,7 +4013,7 @@ fn f() { let expr = $0[1, 2, 3, 4]$0 }
check_hover_range(
r#"
fn f() { let expr = [1, 2, $03, 4]$0 }
"#,
"#,
expect![[r#"
```rust
[i32; 4]
@ -4002,7 +4023,7 @@ fn f() { let expr = [1, 2, $03, 4]$0 }
check_hover_range(
r#"
fn f() { let expr = [1, 2, $03$0, 4] }
"#,
"#,
expect![[r#"
```rust
i32
@ -4016,7 +4037,7 @@ fn f() { let expr = [1, 2, $03$0, 4] }
r#"
fn f<T>(a: &[T]) { }
fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
"#,
"#,
expect![[r#"
```rust
fn f<i32>(&[i32])
@ -4027,7 +4048,7 @@ fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
r#"
fn f<T>(a: &[T]) { }
fn b() { f($0&[1, 2, 3, 4, 5]$0); }
"#,
"#,
expect![[r#"
```rust
&[i32; 5]
@ -4041,20 +4062,20 @@ fn b() { f($0&[1, 2, 3, 4, 5]$0); }
r#"
fn f<T>(a: &[T]) { }
fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
"#,
"#,
);
check_hover_range_no_results(
r#"
fn f<T>$0(a: &[T]) { }
fn b() { f(&[1, 2, 3,$0 4, 5]); }
"#,
"#,
);
check_hover_range_no_results(
r#"
fn $0f() { let expr = [1, 2, 3, 4]$0 }
"#,
"#,
);
}
@ -4064,7 +4085,7 @@ fn $0f() { let expr = [1, 2, 3, 4]$0 }
r#"
fn f<T>(a: &[T]) { }
fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
"#,
"#,
expect![[r#"
```rust
()
@ -4074,11 +4095,68 @@ fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
check_hover_range(
r#"
fn f() { let expr$0 = $0[1, 2, 3, 4] }
"#,
"#,
expect![[r#"
```rust
()
```"#]],
);
}
#[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
```
"#]],
);
}
}