Show coerced types on type hover

This commit is contained in:
Lukas Wirth 2021-08-02 17:10:36 +02:00
parent 432bb222c3
commit 486603d559
3 changed files with 120 additions and 73 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

@ -79,34 +79,20 @@ 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 = 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 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 +100,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 +130,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);
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
@ -173,6 +157,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);
@ -182,7 +167,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);
}
@ -204,10 +189,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,
@ -215,16 +200,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()) {
@ -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
```
"#]],
);
}
}