mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 21:05:02 +00:00
Merge #9954
9954: feat: Show try operator propogated types on ranged hover r=matklad a=Veykril Basically this just shows the type of the inner expression of the `?` expression as well as the type of the expression that the `?` returns from:  Unless both of these types are `core::result::Result` in which case we show the error types only.  If both types are `core::option::Option` with different type params we do not show this special hover either as it would be pointless(instead fallback to default type hover) Very much open to changes to the hover text here(I suppose we also want to show the actual type of the `?` expression, that is its output type?). Fixes #9931 Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
25368d2430
2 changed files with 206 additions and 17 deletions
|
@ -1,7 +1,6 @@
|
||||||
//! Type inference for patterns.
|
//! Type inference for patterns.
|
||||||
|
|
||||||
use std::iter::repeat;
|
use std::{iter::repeat, sync::Arc};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use chalk_ir::Mutability;
|
use chalk_ir::Mutability;
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
|
@ -10,9 +9,10 @@ use hir_def::{
|
||||||
};
|
};
|
||||||
use hir_expand::name::Name;
|
use hir_expand::name::Name;
|
||||||
|
|
||||||
use super::{BindingMode, Expectation, InferenceContext, TypeMismatch};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
infer::{Adjust, Adjustment, AutoBorrow},
|
infer::{
|
||||||
|
Adjust, Adjustment, AutoBorrow, BindingMode, Expectation, InferenceContext, TypeMismatch,
|
||||||
|
},
|
||||||
lower::lower_to_chalk_mutability,
|
lower::lower_to_chalk_mutability,
|
||||||
static_lifetime, Interner, Substitution, Ty, TyBuilder, TyExt, TyKind,
|
static_lifetime, Interner, Substitution, Ty, TyBuilder, TyExt, TyKind,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,8 +12,8 @@ use ide_db::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
|
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
|
||||||
SyntaxKind::*, SyntaxNode, SyntaxToken, T,
|
SyntaxNode, SyntaxToken, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -112,9 +112,8 @@ pub(crate) fn hover(
|
||||||
_ => 1,
|
_ => 1,
|
||||||
})?;
|
})?;
|
||||||
let token = sema.descend_into_macros(token);
|
let token = sema.descend_into_macros(token);
|
||||||
|
|
||||||
let mut range_override = None;
|
|
||||||
let node = token.parent()?;
|
let node = token.parent()?;
|
||||||
|
let mut range_override = None;
|
||||||
let definition = match_ast! {
|
let definition = match_ast! {
|
||||||
match node {
|
match node {
|
||||||
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
|
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
|
||||||
|
@ -138,7 +137,7 @@ pub(crate) fn hover(
|
||||||
),
|
),
|
||||||
_ => {
|
_ => {
|
||||||
// intra-doc links
|
// intra-doc links
|
||||||
if ast::Comment::cast(token.clone()).is_some() {
|
if token.kind() == COMMENT {
|
||||||
cov_mark::hit!(no_highlight_on_comment_hover);
|
cov_mark::hit!(no_highlight_on_comment_hover);
|
||||||
let (attributes, def) = doc_attributes(&sema, &node)?;
|
let (attributes, def) = doc_attributes(&sema, &node)?;
|
||||||
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
|
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
|
||||||
|
@ -233,7 +232,7 @@ fn hover_ranged(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
) -> Option<RangeInfo<HoverResult>> {
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
let expr = file.covering_element(range).ancestors().find_map(|it| {
|
let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
|
||||||
match_ast! {
|
match_ast! {
|
||||||
match it {
|
match it {
|
||||||
ast::Expr(expr) => Some(Either::Left(expr)),
|
ast::Expr(expr) => Some(Either::Left(expr)),
|
||||||
|
@ -242,8 +241,13 @@ fn hover_ranged(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
hover_type_info(sema, config, &expr).map(|it| {
|
let res = match &expr_or_pat {
|
||||||
let range = match expr {
|
Either::Left(ast::Expr::TryExpr(try_expr)) => hover_try_expr(sema, config, try_expr),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let res = res.or_else(|| hover_type_info(sema, config, &expr_or_pat));
|
||||||
|
res.map(|it| {
|
||||||
|
let range = match expr_or_pat {
|
||||||
Either::Left(it) => it.syntax().text_range(),
|
Either::Left(it) => it.syntax().text_range(),
|
||||||
Either::Right(it) => it.syntax().text_range(),
|
Either::Right(it) => it.syntax().text_range(),
|
||||||
};
|
};
|
||||||
|
@ -251,6 +255,97 @@ fn hover_ranged(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hover_try_expr(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
config: &HoverConfig,
|
||||||
|
try_expr: &ast::TryExpr,
|
||||||
|
) -> Option<HoverResult> {
|
||||||
|
let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
|
||||||
|
let mut ancestors = try_expr.syntax().ancestors();
|
||||||
|
let mut body_ty = loop {
|
||||||
|
let next = ancestors.next()?;
|
||||||
|
break match_ast! {
|
||||||
|
match next {
|
||||||
|
ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
|
||||||
|
ast::Item(__) => return None,
|
||||||
|
ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
|
||||||
|
ast::EffectExpr(effect) => if matches!(effect.effect(), ast::Effect::Async(_) | ast::Effect::Try(_)| ast::Effect::Const(_)) {
|
||||||
|
sema.type_of_expr(&effect.block_expr()?.into())?.original
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if inner_ty == body_ty {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut inner_ty = inner_ty;
|
||||||
|
let mut s = "Try Target".to_owned();
|
||||||
|
|
||||||
|
let adts = inner_ty.as_adt().zip(body_ty.as_adt());
|
||||||
|
if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
|
||||||
|
let famous_defs = FamousDefs(sema, sema.scope(&try_expr.syntax()).krate());
|
||||||
|
// special case for two options, there is no value in showing them
|
||||||
|
if let Some(option_enum) = famous_defs.core_option_Option() {
|
||||||
|
if inner == option_enum && body == option_enum {
|
||||||
|
cov_mark::hit!(hover_try_expr_opt_opt);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case two results to show the error variants only
|
||||||
|
if let Some(result_enum) = famous_defs.core_result_Result() {
|
||||||
|
if inner == result_enum && body == result_enum {
|
||||||
|
let error_type_args =
|
||||||
|
inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
|
||||||
|
if let Some((inner, body)) = error_type_args {
|
||||||
|
inner_ty = inner;
|
||||||
|
body_ty = body;
|
||||||
|
s = "Try Error".to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = HoverResult::default();
|
||||||
|
|
||||||
|
let mut targets: Vec<hir::ModuleDef> = Vec::new();
|
||||||
|
let mut push_new_def = |item: hir::ModuleDef| {
|
||||||
|
if !targets.contains(&item) {
|
||||||
|
targets.push(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
|
||||||
|
walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
|
||||||
|
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
|
||||||
|
|
||||||
|
let inner_ty = inner_ty.display(sema.db).to_string();
|
||||||
|
let body_ty = body_ty.display(sema.db).to_string();
|
||||||
|
let ty_len_max = inner_ty.len().max(body_ty.len());
|
||||||
|
|
||||||
|
let l = "Propagated as: ".len() - " Type: ".len();
|
||||||
|
let static_text_len_diff = l as isize - s.len() as isize;
|
||||||
|
let tpad = static_text_len_diff.max(0) as usize;
|
||||||
|
let ppad = static_text_len_diff.min(0).abs() as usize;
|
||||||
|
|
||||||
|
res.markup = format!(
|
||||||
|
"{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
|
||||||
|
s,
|
||||||
|
inner_ty,
|
||||||
|
body_ty,
|
||||||
|
pad0 = ty_len_max + tpad,
|
||||||
|
pad1 = ty_len_max + ppad,
|
||||||
|
bt_start = if config.markdown() { "```text\n" } else { "" },
|
||||||
|
bt_end = if config.markdown() { "```\n" } else { "" }
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
fn hover_type_info(
|
fn hover_type_info(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
|
@ -274,13 +369,15 @@ fn hover_type_info(
|
||||||
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
|
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
|
||||||
let original = original.display(sema.db).to_string();
|
let original = original.display(sema.db).to_string();
|
||||||
let adjusted = adjusted_ty.display(sema.db).to_string();
|
let adjusted = adjusted_ty.display(sema.db).to_string();
|
||||||
|
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
|
||||||
format!(
|
format!(
|
||||||
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
|
"{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
|
||||||
uncoerced = original,
|
original,
|
||||||
coerced = adjusted,
|
adjusted,
|
||||||
// 6 base padding for difference of length of the two text prefixes
|
apad = static_text_diff_len + adjusted.len().max(original.len()),
|
||||||
apad = 6 + adjusted.len().max(original.len()),
|
|
||||||
opad = original.len(),
|
opad = original.len(),
|
||||||
|
bt_start = if config.markdown() { "```text\n" } else { "" },
|
||||||
|
bt_end = if config.markdown() { "```\n" } else { "" }
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
|
@ -4257,4 +4354,96 @@ fn foo() {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_try_expr_res() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
//- minicore:result
|
||||||
|
struct FooError;
|
||||||
|
|
||||||
|
fn foo() -> Result<(), FooError> {
|
||||||
|
Ok($0Result::<(), FooError>::Ok(())?$0)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
()
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
//- minicore:result
|
||||||
|
struct FooError;
|
||||||
|
struct BarError;
|
||||||
|
|
||||||
|
fn foo() -> Result<(), FooError> {
|
||||||
|
Ok($0Result::<(), BarError>::Ok(())?$0)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```text
|
||||||
|
Try Error Type: BarError
|
||||||
|
Propagated as: FooError
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_try_expr() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
struct NotResult<T, U>(T, U);
|
||||||
|
struct Short;
|
||||||
|
struct Looooong;
|
||||||
|
|
||||||
|
fn foo() -> NotResult<(), Looooong> {
|
||||||
|
$0NotResult((), Short)?$0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```text
|
||||||
|
Try Target Type: NotResult<(), Short>
|
||||||
|
Propagated as: NotResult<(), Looooong>
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
struct NotResult<T, U>(T, U);
|
||||||
|
struct Short;
|
||||||
|
struct Looooong;
|
||||||
|
|
||||||
|
fn foo() -> NotResult<(), Short> {
|
||||||
|
$0NotResult((), Looooong)?$0;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```text
|
||||||
|
Try Target Type: NotResult<(), Looooong>
|
||||||
|
Propagated as: NotResult<(), Short>
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_try_expr_option() {
|
||||||
|
cov_mark::check!(hover_try_expr_opt_opt);
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
//- minicore: option, try
|
||||||
|
|
||||||
|
fn foo() -> Option<()> {
|
||||||
|
$0Some(0)?$0;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
<Option<i32> as Try>::Output
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue