Skip to content

Commit 3a27518

Browse files
committed
Auto merge of #14690 - HKalbasi:closure-hover, r=HKalbasi
Add hover for closure
2 parents 7bcb4c2 + 5df545b commit 3a27518

File tree

7 files changed

+249
-7
lines changed

7 files changed

+249
-7
lines changed

crates/hir-ty/src/infer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ mod path;
6262
mod expr;
6363
mod pat;
6464
mod coerce;
65-
mod closure;
65+
pub(crate) mod closure;
6666
mod mutability;
6767

6868
/// The entry point of type inference.
@@ -426,7 +426,7 @@ impl InferenceResult {
426426
_ => None,
427427
})
428428
}
429-
pub(crate) fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
429+
pub fn closure_info(&self, closure: &ClosureId) -> &(Vec<CapturedItem>, FnTrait) {
430430
self.closure_info.get(closure).unwrap()
431431
}
432432
}

crates/hir-ty/src/infer/closure.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{cmp, collections::HashMap, convert::Infallible, mem};
44

55
use chalk_ir::{cast::Cast, AliasEq, AliasTy, FnSubst, Mutability, TyKind, WhereClause};
66
use hir_def::{
7+
data::adt::VariantData,
78
hir::{
89
Array, BinaryOp, BindingAnnotation, BindingId, CaptureBy, Expr, ExprId, Pat, PatId,
910
Statement, UnaryOp,
@@ -18,6 +19,7 @@ use smallvec::SmallVec;
1819
use stdx::never;
1920

2021
use crate::{
22+
db::HirDatabase,
2123
mir::{BorrowKind, MirSpan, ProjectionElem},
2224
static_lifetime, to_chalk_trait_id,
2325
traits::FnTrait,
@@ -146,13 +148,81 @@ pub(crate) enum CaptureKind {
146148
}
147149

148150
#[derive(Debug, Clone, PartialEq, Eq)]
149-
pub(crate) struct CapturedItem {
151+
pub struct CapturedItem {
150152
pub(crate) place: HirPlace,
151153
pub(crate) kind: CaptureKind,
152154
pub(crate) span: MirSpan,
153155
pub(crate) ty: Ty,
154156
}
155157

158+
impl CapturedItem {
159+
pub fn display_kind(&self) -> &'static str {
160+
match self.kind {
161+
CaptureKind::ByRef(k) => match k {
162+
BorrowKind::Shared => "immutable borrow",
163+
BorrowKind::Shallow => {
164+
never!("shallow borrow should not happen in closure captures");
165+
"shallow borrow"
166+
},
167+
BorrowKind::Unique => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
168+
BorrowKind::Mut { .. } => "mutable borrow",
169+
},
170+
CaptureKind::ByValue => "move",
171+
}
172+
}
173+
174+
pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
175+
let owner = db.lookup_intern_closure(owner.into()).0;
176+
let body = db.body(owner);
177+
let mut result = body[self.place.local].name.to_string();
178+
let mut field_need_paren = false;
179+
for proj in &self.place.projections {
180+
match proj {
181+
ProjectionElem::Deref => {
182+
result = format!("*{result}");
183+
field_need_paren = true;
184+
}
185+
ProjectionElem::Field(f) => {
186+
if field_need_paren {
187+
result = format!("({result})");
188+
}
189+
let variant_data = f.parent.variant_data(db.upcast());
190+
let field = match &*variant_data {
191+
VariantData::Record(fields) => fields[f.local_id]
192+
.name
193+
.as_str()
194+
.unwrap_or("[missing field]")
195+
.to_string(),
196+
VariantData::Tuple(fields) => fields
197+
.iter()
198+
.position(|x| x.0 == f.local_id)
199+
.unwrap_or_default()
200+
.to_string(),
201+
VariantData::Unit => "[missing field]".to_string(),
202+
};
203+
result = format!("{result}.{field}");
204+
field_need_paren = false;
205+
}
206+
&ProjectionElem::TupleOrClosureField(field) => {
207+
if field_need_paren {
208+
result = format!("({result})");
209+
}
210+
result = format!("{result}.{field}");
211+
field_need_paren = false;
212+
}
213+
ProjectionElem::Index(_)
214+
| ProjectionElem::ConstantIndex { .. }
215+
| ProjectionElem::Subslice { .. }
216+
| ProjectionElem::OpaqueCast(_) => {
217+
never!("Not happen in closure capture");
218+
continue;
219+
}
220+
}
221+
}
222+
result
223+
}
224+
}
225+
156226
#[derive(Debug, Clone, PartialEq, Eq)]
157227
pub(crate) struct CapturedItemWithoutTy {
158228
pub(crate) place: HirPlace,

crates/hir-ty/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ pub use autoderef::autoderef;
6060
pub use builder::{ParamKind, TyBuilder};
6161
pub use chalk_ext::*;
6262
pub use infer::{
63-
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
64-
InferenceResult, OverloadedDeref, PointerCast,
63+
closure::CapturedItem, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode,
64+
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
6565
};
6666
pub use interner::Interner;
6767
pub use lower::{

crates/hir/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3174,6 +3174,46 @@ impl TraitRef {
31743174
}
31753175
}
31763176

3177+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3178+
pub struct Closure {
3179+
id: ClosureId,
3180+
subst: Substitution,
3181+
}
3182+
3183+
impl From<Closure> for ClosureId {
3184+
fn from(value: Closure) -> Self {
3185+
value.id
3186+
}
3187+
}
3188+
3189+
impl Closure {
3190+
fn as_ty(self) -> Ty {
3191+
TyKind::Closure(self.id, self.subst).intern(Interner)
3192+
}
3193+
3194+
pub fn display_with_id(&self, db: &dyn HirDatabase) -> String {
3195+
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ClosureWithId).to_string()
3196+
}
3197+
3198+
pub fn display_with_impl(&self, db: &dyn HirDatabase) -> String {
3199+
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
3200+
}
3201+
3202+
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<hir_ty::CapturedItem> {
3203+
let owner = db.lookup_intern_closure((self.id).into()).0;
3204+
let infer = &db.infer(owner);
3205+
let info = infer.closure_info(&self.id);
3206+
info.0.clone()
3207+
}
3208+
3209+
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
3210+
let owner = db.lookup_intern_closure((self.id).into()).0;
3211+
let infer = &db.infer(owner);
3212+
let info = infer.closure_info(&self.id);
3213+
info.1
3214+
}
3215+
}
3216+
31773217
#[derive(Clone, PartialEq, Eq, Debug)]
31783218
pub struct Type {
31793219
env: Arc<TraitEnvironment>,
@@ -3463,6 +3503,13 @@ impl Type {
34633503
matches!(self.ty.kind(Interner), TyKind::Closure { .. })
34643504
}
34653505

3506+
pub fn as_closure(&self) -> Option<Closure> {
3507+
match self.ty.kind(Interner) {
3508+
TyKind::Closure(id, subst) => Some(Closure { id: *id, subst: subst.clone() }),
3509+
_ => None,
3510+
}
3511+
}
3512+
34663513
pub fn is_fn(&self) -> bool {
34673514
matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
34683515
}
@@ -4016,6 +4063,10 @@ impl Type {
40164063
.map(|id| TypeOrConstParam { id }.split(db).either_into())
40174064
.collect()
40184065
}
4066+
4067+
pub fn layout(&self, db: &dyn HirDatabase) -> Result<Layout, LayoutError> {
4068+
layout_of_ty(db, &self.ty, self.env.krate)
4069+
}
40194070
}
40204071

40214072
// FIXME: Document this

crates/ide/src/hover.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ fn hover_simple(
119119
| T![crate]
120120
| T![Self]
121121
| T![_] => 4,
122-
// index and prefix ops
123-
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
122+
// index and prefix ops and closure pipe
123+
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3,
124124
kind if kind.is_keyword() => 2,
125125
T!['('] | T![')'] => 2,
126126
kind if kind.is_trivia() => 0,
@@ -219,6 +219,16 @@ fn hover_simple(
219219
};
220220
render::type_info_of(sema, config, &Either::Left(call_expr))
221221
})
222+
})
223+
// try closure
224+
.or_else(|| {
225+
descended().find_map(|token| {
226+
if token.kind() != T![|] {
227+
return None;
228+
}
229+
let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
230+
render::closure_expr(sema, c)
231+
})
222232
});
223233

224234
result.map(|mut res: HoverResult| {

crates/ide/src/hover/render.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,38 @@ pub(super) fn type_info_of(
4242
type_info(sema, _config, original, adjusted)
4343
}
4444

45+
pub(super) fn closure_expr(
46+
sema: &Semantics<'_, RootDatabase>,
47+
c: ast::ClosureExpr,
48+
) -> Option<HoverResult> {
49+
let ty = &sema.type_of_expr(&c.into())?.original;
50+
let layout = ty
51+
.layout(sema.db)
52+
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
53+
.unwrap_or_default();
54+
let c = ty.as_closure()?;
55+
let mut captures = c
56+
.captured_items(sema.db)
57+
.into_iter()
58+
.map(|x| {
59+
format!("* `{}` by {}", x.display_place(c.clone().into(), sema.db), x.display_kind())
60+
})
61+
.join("\n");
62+
if captures.trim().is_empty() {
63+
captures = "This closure captures nothing".to_string();
64+
}
65+
let mut res = HoverResult::default();
66+
res.markup = format!(
67+
"```rust\n{}{}\n{}\n```\n\n## Captures\n{}",
68+
c.display_with_id(sema.db),
69+
layout,
70+
c.display_with_impl(sema.db),
71+
captures,
72+
)
73+
.into();
74+
Some(res)
75+
}
76+
4577
pub(super) fn try_expr(
4678
sema: &Semantics<'_, RootDatabase>,
4779
_config: &HoverConfig,

crates/ide/src/hover/tests.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,85 @@ fn main() {
198198
);
199199
}
200200

201+
#[test]
202+
fn hover_closure() {
203+
check(
204+
r#"
205+
//- minicore: copy
206+
fn main() {
207+
let x = 2;
208+
let y = $0|z| x + z;
209+
}
210+
"#,
211+
expect![[r#"
212+
*|*
213+
```rust
214+
{closure#0} // size = 8, align = 8
215+
impl Fn(i32) -> i32
216+
```
217+
218+
## Captures
219+
* `x` by immutable borrow
220+
"#]],
221+
);
222+
223+
check(
224+
r#"
225+
//- minicore: copy
226+
fn foo(x: impl Fn(i32) -> i32) {
227+
228+
}
229+
fn main() {
230+
foo($0|x: i32| x)
231+
}
232+
"#,
233+
expect![[r#"
234+
*|*
235+
```rust
236+
{closure#0} // size = 0, align = 1
237+
impl Fn(i32) -> i32
238+
```
239+
240+
## Captures
241+
This closure captures nothing
242+
"#]],
243+
);
244+
245+
check(
246+
r#"
247+
//- minicore: copy
248+
249+
struct Z { f: i32 }
250+
251+
struct Y(&'static mut Z)
252+
253+
struct X {
254+
f1: Y,
255+
f2: (Y, Y),
256+
}
257+
258+
fn main() {
259+
let x: X;
260+
let y = $0|| {
261+
x.f1;
262+
&mut x.f2.0 .0.f;
263+
};
264+
}
265+
"#,
266+
expect![[r#"
267+
*|*
268+
```rust
269+
{closure#0} // size = 16, align = 8
270+
impl FnOnce()
271+
```
272+
273+
## Captures
274+
* `x.f1` by move
275+
* `(*x.f2.0.0).f` by mutable borrow
276+
"#]],
277+
);
278+
}
279+
201280
#[test]
202281
fn hover_shows_long_type_of_an_expression() {
203282
check(

0 commit comments

Comments
 (0)