Add hover for closure

This commit is contained in:
hkalbasi 2023-04-30 14:31:43 +03:30
parent 370b72c7dd
commit 5df545b3f0
7 changed files with 249 additions and 7 deletions

View File

@ -62,7 +62,7 @@ mod path;
mod expr;
mod pat;
mod coerce;
mod closure;
pub(crate) mod closure;
mod mutability;
/// The entry point of type inference.
@ -426,7 +426,7 @@ impl InferenceResult {
_ => None,
})
}
pub(crate) fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
pub fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
self.closure_info.get(closure).unwrap()
}
}

View File

@ -4,6 +4,7 @@ use std::{cmp, collections::HashMap, convert::Infallible, mem};
use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, Mutability, TyKind, WhereClause};
use hir_def::{
data::adt::VariantData,
hir::{
Array, BinaryOp, BindingAnnotation, BindingId, CaptureBy, Expr, ExprId, Pat, PatId,
Statement, UnaryOp,
@ -18,6 +19,7 @@ use smallvec::SmallVec;
use stdx::never;
use crate::{
db::HirDatabase,
mir::{BorrowKind, MirSpan, ProjectionElem},
static_lifetime, to_chalk_trait_id,
traits::FnTrait,
@ -146,13 +148,81 @@ pub(crate) enum CaptureKind {
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CapturedItem {
pub struct CapturedItem {
pub(crate) place: HirPlace,
pub(crate) kind: CaptureKind,
pub(crate) span: MirSpan,
pub(crate) ty: Ty,
}
impl CapturedItem {
pub fn display_kind(&self) -> &'static str {
match self.kind {
CaptureKind::ByRef(k) => match k {
BorrowKind::Shared => "immutable borrow",
BorrowKind::Shallow => {
never!("shallow borrow should not happen in closure captures");
"shallow borrow"
},
BorrowKind::Unique => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
BorrowKind::Mut { .. } => "mutable borrow",
},
CaptureKind::ByValue => "move",
}
}
pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
let owner = db.lookup_intern_closure(owner.into()).0;
let body = db.body(owner);
let mut result = body[self.place.local].name.to_string();
let mut field_need_paren = false;
for proj in &self.place.projections {
match proj {
ProjectionElem::Deref => {
result = format!("*{result}");
field_need_paren = true;
}
ProjectionElem::Field(f) => {
if field_need_paren {
result = format!("({result})");
}
let variant_data = f.parent.variant_data(db.upcast());
let field = match &*variant_data {
VariantData::Record(fields) => fields[f.local_id]
.name
.as_str()
.unwrap_or("[missing field]")
.to_string(),
VariantData::Tuple(fields) => fields
.iter()
.position(|x| x.0 == f.local_id)
.unwrap_or_default()
.to_string(),
VariantData::Unit => "[missing field]".to_string(),
};
result = format!("{result}.{field}");
field_need_paren = false;
}
&ProjectionElem::TupleOrClosureField(field) => {
if field_need_paren {
result = format!("({result})");
}
result = format!("{result}.{field}");
field_need_paren = false;
}
ProjectionElem::Index(_)
| ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Subslice { .. }
| ProjectionElem::OpaqueCast(_) => {
never!("Not happen in closure capture");
continue;
}
}
}
result
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CapturedItemWithoutTy {
pub(crate) place: HirPlace,

View File

@ -60,8 +60,8 @@ pub use autoderef::autoderef;
pub use builder::{ParamKind, TyBuilder};
pub use chalk_ext::*;
pub use infer::{
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
InferenceResult, OverloadedDeref, PointerCast,
closure::CapturedItem, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode,
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
};
pub use interner::Interner;
pub use lower::{

View File

@ -3174,6 +3174,46 @@ impl TraitRef {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Closure {
id: ClosureId,
subst: Substitution,
}
impl From<Closure> for ClosureId {
fn from(value: Closure) -> Self {
value.id
}
}
impl Closure {
fn as_ty(self) -> Ty {
TyKind::Closure(self.id, self.subst).intern(Interner)
}
pub fn display_with_id(&self, db: &dyn HirDatabase) -> String {
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string()
}
pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String {
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
}
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<hir_ty::CapturedItem> {
let owner = db.lookup_intern_closure((self.id).into()).0;
let infer = &db.infer(owner);
let info = infer.closure_info(&self.id);
info.0.clone()
}
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
let owner = db.lookup_intern_closure((self.id).into()).0;
let infer = &db.infer(owner);
let info = infer.closure_info(&self.id);
info.1
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Type {
env: Arc<TraitEnvironment>,
@ -3463,6 +3503,13 @@ impl Type {
matches!(self.ty.kind(Interner), TyKind::Closure { .. })
}
pub fn as_closure(&self) -> Option<Closure> {
match self.ty.kind(Interner) {
TyKind::Closure(id, subst) => Some(Closure { id: *id, subst: subst.clone() }),
_ => None,
}
}
pub fn is_fn(&self) -> bool {
matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
}
@ -4016,6 +4063,10 @@ impl Type {
.map(|id| TypeOrConstParam { id }.split(db).either_into())
.collect()
}
pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
layout_of_ty(db, &self.ty, self.env.krate)
}
}
// FIXME: Document this

View File

@ -119,8 +119,8 @@ fn hover_simple(
| T![crate]
| T![Self]
| T![_] => 4,
// index and prefix ops
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
// index and prefix ops and closure pipe
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3,
kind if kind.is_keyword() => 2,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
@ -219,6 +219,16 @@ fn hover_simple(
};
render::type_info_of(sema, config, &Either::Left(call_expr))
})
})
// try closure
.or_else(|| {
descended().find_map(|token| {
if token.kind() != T![|] {
return None;
}
let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
render::closure_expr(sema, c)
})
});
result.map(|mut res: HoverResult| {

View File

@ -42,6 +42,38 @@ pub(super) fn type_info_of(
type_info(sema, _config, original, adjusted)
}
pub(super) fn closure_expr(
sema: &Semantics<'_, RootDatabase>,
c: ast::ClosureExpr,
) -> Option<HoverResult> {
let ty = &sema.type_of_expr(&c.into())?.original;
let layout = ty
.layout(sema.db)
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
.unwrap_or_default();
let c = ty.as_closure()?;
let mut captures = c
.captured_items(sema.db)
.into_iter()
.map(|x| {
format!("* `{}` by {}", x.display_place(c.clone().into(), sema.db), x.display_kind())
})
.join("\n");
if captures.trim().is_empty() {
captures = "This closure captures nothing".to_string();
}
let mut res = HoverResult::default();
res.markup = format!(
"```rust\n{}{}\n{}\n```\n\n## Captures\n{}",
c.display_with_id(sema.db),
layout,
c.display_with_impl(sema.db),
captures,
)
.into();
Some(res)
}
pub(super) fn try_expr(
sema: &Semantics<'_, RootDatabase>,
_config: &HoverConfig,

View File

@ -198,6 +198,85 @@ fn main() {
);
}
#[test]
fn hover_closure() {
check(
r#"
//- minicore: copy
fn main() {
let x = 2;
let y = $0|z| x + z;
}
"#,
expect![[r#"
*|*
```rust
{closure#0} // size = 8, align = 8
impl Fn(i32) -> i32
```
## Captures
* `x` by immutable borrow
"#]],
);
check(
r#"
//- minicore: copy
fn foo(x: impl Fn(i32) -> i32) {
}
fn main() {
foo($0|x: i32| x)
}
"#,
expect![[r#"
*|*
```rust
{closure#0} // size = 0, align = 1
impl Fn(i32) -> i32
```
## Captures
This closure captures nothing
"#]],
);
check(
r#"
//- minicore: copy
struct Z { f: i32 }
struct Y(&'static mut Z)
struct X {
f1: Y,
f2: (Y, Y),
}
fn main() {
let x: X;
let y = $0|| {
x.f1;
&mut x.f2.0 .0.f;
};
}
"#,
expect![[r#"
*|*
```rust
{closure#0} // size = 16, align = 8
impl FnOnce()
```
## Captures
* `x.f1` by move
* `(*x.f2.0.0).f` by mutable borrow
"#]],
);
}
#[test]
fn hover_shows_long_type_of_an_expression() {
check(