mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-26 20:09:19 +00:00
Show coerced types on type hover
This commit is contained in:
parent
432bb222c3
commit
486603d559
3 changed files with 120 additions and 73 deletions
|
@ -225,7 +225,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
||||||
self.imp.type_of_pat(pat)
|
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)
|
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)
|
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)
|
self.analyze(pat.syntax()).type_of_pat_with_coercion(self.db, pat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,15 +147,15 @@ impl SourceAnalyzer {
|
||||||
&self,
|
&self,
|
||||||
db: &dyn HirDatabase,
|
db: &dyn HirDatabase,
|
||||||
pat: &ast::Pat,
|
pat: &ast::Pat,
|
||||||
) -> Option<Type> {
|
) -> Option<(Type, bool)> {
|
||||||
let pat_id = self.pat_id(pat)?;
|
let pat_id = self.pat_id(pat)?;
|
||||||
let infer = self.infer.as_ref()?;
|
let infer = self.infer.as_ref()?;
|
||||||
let ty = infer
|
let (ty, coerced) = infer
|
||||||
.pat_adjustments
|
.pat_adjustments
|
||||||
.get(&pat_id)
|
.get(&pat_id)
|
||||||
.and_then(|adjusts| adjusts.last().map(|adjust| &adjust.target))
|
.and_then(|adjusts| adjusts.last().map(|adjust| (&adjust.target, true)))
|
||||||
.unwrap_or_else(|| &infer[pat_id]);
|
.unwrap_or_else(|| (&infer[pat_id], false));
|
||||||
Type::new_with_resolver(db, &self.resolver, ty.clone())
|
Type::new_with_resolver(db, &self.resolver, ty.clone()).zip(Some(coerced))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn type_of_self(
|
pub(crate) fn type_of_self(
|
||||||
|
|
|
@ -79,34 +79,20 @@ pub struct HoverResult {
|
||||||
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
|
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
|
||||||
pub(crate) fn hover(
|
pub(crate) fn hover(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
range: FileRange,
|
FileRange { file_id, range }: FileRange,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
) -> Option<RangeInfo<HoverResult>> {
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
let sema = hir::Semantics::new(db);
|
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.
|
let offset = if range.is_empty() {
|
||||||
if !range.range.is_empty() {
|
range.start()
|
||||||
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))
|
|
||||||
} else {
|
} else {
|
||||||
ty.display(db).to_string().into()
|
let expr = find_node_at_range::<ast::Expr>(&file, range).map(Either::Left)?;
|
||||||
|
return hover_type_info(&sema, config, expr).map(|it| RangeInfo::new(range, it));
|
||||||
};
|
};
|
||||||
|
|
||||||
return Some(RangeInfo::new(range.range, res));
|
let token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
|
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
|
||||||
T!['('] | T![')'] => 2,
|
T!['('] | T![')'] => 2,
|
||||||
kind if kind.is_trivia() => 0,
|
kind if kind.is_trivia() => 0,
|
||||||
|
@ -114,8 +100,6 @@ pub(crate) fn hover(
|
||||||
})?;
|
})?;
|
||||||
let token = sema.descend_into_macros(token);
|
let token = sema.descend_into_macros(token);
|
||||||
|
|
||||||
let mut res = HoverResult::default();
|
|
||||||
|
|
||||||
let node = token.parent()?;
|
let node = token.parent()?;
|
||||||
let mut range = None;
|
let mut range = None;
|
||||||
let definition = match_ast! {
|
let definition = match_ast! {
|
||||||
|
@ -146,8 +130,8 @@ pub(crate) fn hover(
|
||||||
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
|
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
|
||||||
let (idl_range, link, ns) =
|
let (idl_range, link, ns) =
|
||||||
extract_definitions_from_docs(&docs).into_iter().find_map(|(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)?;
|
let mapped = doc_mapping.map(range)?;
|
||||||
(file_id == position.file_id.into() && mapped_range.contains(position.offset)).then(||(mapped_range, link, ns))
|
(mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
|
||||||
})?;
|
})?;
|
||||||
range = Some(idl_range);
|
range = Some(idl_range);
|
||||||
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
|
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
|
||||||
|
@ -173,6 +157,7 @@ pub(crate) fn hover(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) {
|
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);
|
res.markup = process_markup(sema.db, definition, &markup, config);
|
||||||
if let Some(action) = show_implementations_action(db, definition) {
|
if let Some(action) = show_implementations_action(db, definition) {
|
||||||
res.actions.push(action);
|
res.actions.push(action);
|
||||||
|
@ -182,7 +167,7 @@ pub(crate) fn hover(
|
||||||
res.actions.push(action);
|
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);
|
res.actions.push(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,10 +189,10 @@ pub(crate) fn hover(
|
||||||
.take_while(|it| !ast::Item::can_cast(it.kind()))
|
.take_while(|it| !ast::Item::can_cast(it.kind()))
|
||||||
.find(|n| ast::Expr::can_cast(n.kind()) || ast::Pat::can_cast(n.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 {
|
match node {
|
||||||
ast::Expr(it) => sema.type_of_expr(&it)?,
|
ast::Expr(it) => Either::Left(it),
|
||||||
ast::Pat(it) => sema.type_of_pat(&it)?,
|
ast::Pat(it) => Either::Right(it),
|
||||||
// If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
|
// 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.
|
// (e.g expanding a builtin macro). So we give up here.
|
||||||
ast::MacroCall(_it) => return None,
|
ast::MacroCall(_it) => return None,
|
||||||
|
@ -215,16 +200,48 @@ pub(crate) fn hover(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
res.markup = if config.markdown() {
|
let res = hover_type_info(&sema, config, expr_or_pat)?;
|
||||||
Markup::fenced_block(&ty.display(db))
|
|
||||||
} else {
|
|
||||||
ty.display(db).to_string().into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let range = sema.original_range(&node).range;
|
let range = sema.original_range(&node).range;
|
||||||
Some(RangeInfo::new(range, res))
|
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>> {
|
fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
|
||||||
let (path, tt) = attr.as_simple_call()?;
|
let (path, tt) = attr.as_simple_call()?;
|
||||||
if !tt.syntax().text_range().contains(token.text_range().start()) {
|
if !tt.syntax().text_range().contains(token.text_range().start()) {
|
||||||
|
@ -4078,4 +4095,34 @@ fn f() { let expr$0 = $0[1, 2, 3, 4] }
|
||||||
```"#]],
|
```"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_range_shows_coercions_if_applicable() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn foo() {
|
||||||
|
let x: &u32 = $0&&&&&0$0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```
|
||||||
|
Type: &&&&&u32
|
||||||
|
Coerced to: &u32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn foo() {
|
||||||
|
let x: *const u32 = $0&0$0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```
|
||||||
|
Type: &u32
|
||||||
|
Coerced to: *const u32
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue