mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 21:05:02 +00:00
Merge #9761
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.   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:
commit
cae54d86d8
3 changed files with 157 additions and 79 deletions
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
```
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue