Skip to content

Commit

Permalink
Auto merge of #14690 - HKalbasi:closure-hover, r=HKalbasi
Browse files Browse the repository at this point in the history
Add hover for closure
  • Loading branch information
bors committed Apr 30, 2023
2 parents 7bcb4c2 + 5df545b commit 3a27518
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 7 deletions.
4 changes: 2 additions & 2 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()
}
}
Expand Down
72 changes: 71 additions & 1 deletion crates/hir-ty/src/infer/closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions crates/hir-ty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
51 changes: 51 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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 { .. })
}
Expand Down Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions crates/ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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| {
Expand Down
32 changes: 32 additions & 0 deletions crates/ide/src/hover/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
79 changes: 79 additions & 0 deletions crates/ide/src/hover/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 3a27518

Please sign in to comment.