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: ![Code_wIrCxMqLH9](https://user-images.githubusercontent.com/3757771/130111025-f7ee0742-214a-493b-947a-b4a671e4be92.png) Unless both of these types are `core::result::Result` in which case we show the error types only. ![Code_Xruw5FCBNI](https://user-images.githubusercontent.com/3757771/130111024-f9caef82-92e4-4070-b3dd-f2ff9e5d87a9.png) 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
@ -1,7 +1,6 @@
|
||||
//! Type inference for patterns.
|
||||
|
||||
use std::iter::repeat;
|
||||
use std::sync::Arc;
|
||||
use std::{iter::repeat, sync::Arc};
|
||||
|
||||
use chalk_ir::Mutability;
|
||||
use hir_def::{
|
||||
@ -10,9 +9,10 @@
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
|
||||
use super::{BindingMode, Expectation, InferenceContext, TypeMismatch};
|
||||
use crate::{
|
||||
infer::{Adjust, Adjustment, AutoBorrow},
|
||||
infer::{
|
||||
Adjust, Adjustment, AutoBorrow, BindingMode, Expectation, InferenceContext, TypeMismatch,
|
||||
},
|
||||
lower::lower_to_chalk_mutability,
|
||||
static_lifetime, Interner, Substitution, Ty, TyBuilder, TyExt, TyKind,
|
||||
};
|
||||
|
@ -12,8 +12,8 @@
|
||||
use itertools::Itertools;
|
||||
use stdx::format_to;
|
||||
use syntax::{
|
||||
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
|
||||
SyntaxKind::*, SyntaxNode, SyntaxToken, T,
|
||||
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken, T,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -112,9 +112,8 @@ pub(crate) fn hover(
|
||||
_ => 1,
|
||||
})?;
|
||||
let token = sema.descend_into_macros(token);
|
||||
|
||||
let mut range_override = None;
|
||||
let node = token.parent()?;
|
||||
let mut range_override = None;
|
||||
let definition = match_ast! {
|
||||
match node {
|
||||
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
|
||||
@ -138,7 +137,7 @@ pub(crate) fn hover(
|
||||
),
|
||||
_ => {
|
||||
// intra-doc links
|
||||
if ast::Comment::cast(token.clone()).is_some() {
|
||||
if token.kind() == COMMENT {
|
||||
cov_mark::hit!(no_highlight_on_comment_hover);
|
||||
let (attributes, def) = doc_attributes(&sema, &node)?;
|
||||
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
|
||||
@ -233,7 +232,7 @@ fn hover_ranged(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
config: &HoverConfig,
|
||||
) -> 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 it {
|
||||
ast::Expr(expr) => Some(Either::Left(expr)),
|
||||
@ -242,8 +241,13 @@ fn hover_ranged(
|
||||
}
|
||||
}
|
||||
})?;
|
||||
hover_type_info(sema, config, &expr).map(|it| {
|
||||
let range = match expr {
|
||||
let res = match &expr_or_pat {
|
||||
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::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(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
config: &HoverConfig,
|
||||
@ -274,13 +369,15 @@ fn hover_type_info(
|
||||
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
|
||||
let original = original.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!(
|
||||
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
|
||||
uncoerced = original,
|
||||
coerced = adjusted,
|
||||
// 6 base padding for difference of length of the two text prefixes
|
||||
apad = 6 + adjusted.len().max(original.len()),
|
||||
"{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
|
||||
original,
|
||||
adjusted,
|
||||
apad = static_text_diff_len + adjusted.len().max(original.len()),
|
||||
opad = original.len(),
|
||||
bt_start = if config.markdown() { "```text\n" } else { "" },
|
||||
bt_end = if config.markdown() { "```\n" } else { "" }
|
||||
)
|
||||
.into()
|
||||
} 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…
Reference in New Issue
Block a user