Skip to content

Commit 88d2125

Browse files
committed
Auto merge of rust-lang#14811 - Veykril:closure-hover, r=HKalbasi
feat: Render hover actions for closure captures and sig Also special cases closures for ranged type hover to render the closure hover instead
2 parents 2f8cd66 + ba8bcde commit 88d2125

File tree

5 files changed

+173
-43
lines changed

5 files changed

+173
-43
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ impl CapturedItem {
166166
self.place.local
167167
}
168168

169+
pub fn ty(&self, subst: &Substitution) -> Ty {
170+
self.ty.clone().substitute(Interner, utils::ClosureSubst(subst).parent_subst())
171+
}
172+
169173
pub fn kind(&self) -> CaptureKind {
170174
self.kind
171175
}

crates/hir/src/lib.rs

+14
Original file line numberDiff line numberDiff line change
@@ -3221,6 +3221,20 @@ impl Closure {
32213221
.collect()
32223222
}
32233223

3224+
pub fn capture_types(&self, db: &dyn HirDatabase) -> Vec<Type> {
3225+
let owner = db.lookup_intern_closure((self.id).into()).0;
3226+
let infer = &db.infer(owner);
3227+
let (captures, _) = infer.closure_info(&self.id);
3228+
captures
3229+
.iter()
3230+
.cloned()
3231+
.map(|capture| Type {
3232+
env: db.trait_environment_for_body(owner),
3233+
ty: capture.ty(&self.subst),
3234+
})
3235+
.collect()
3236+
}
3237+
32243238
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
32253239
let owner = db.lookup_intern_closure((self.id).into()).0;
32263240
let infer = &db.infer(owner);

crates/ide/src/highlight_related.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ pub(crate) fn highlight_related(
5454

5555
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
5656
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
57-
T![->] | T![|] => 3,
58-
kind if kind.is_keyword() => 2,
59-
IDENT | INT_NUMBER => 1,
57+
T![->] => 4,
58+
kind if kind.is_keyword() => 3,
59+
IDENT | INT_NUMBER => 2,
60+
T![|] => 1,
6061
_ => 0,
6162
})?;
6263
// most if not all of these should be re-implemented with information seeded from hir

crates/ide/src/hover/render.rs

+73-40
Original file line numberDiff line numberDiff line change
@@ -35,53 +35,20 @@ pub(super) fn type_info_of(
3535
_config: &HoverConfig,
3636
expr_or_pat: &Either<ast::Expr, ast::Pat>,
3737
) -> Option<HoverResult> {
38-
let TypeInfo { original, adjusted } = match expr_or_pat {
38+
let ty_info = match expr_or_pat {
3939
Either::Left(expr) => sema.type_of_expr(expr)?,
4040
Either::Right(pat) => sema.type_of_pat(pat)?,
4141
};
42-
type_info(sema, _config, original, adjusted)
42+
type_info(sema, _config, ty_info)
4343
}
4444

4545
pub(super) fn closure_expr(
4646
sema: &Semantics<'_, RootDatabase>,
4747
config: &HoverConfig,
4848
c: ast::ClosureExpr,
4949
) -> Option<HoverResult> {
50-
let ty = &sema.type_of_expr(&c.into())?.original;
51-
let layout = if config.memory_layout {
52-
ty.layout(sema.db)
53-
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
54-
.unwrap_or_default()
55-
} else {
56-
String::default()
57-
};
58-
let c = ty.as_closure()?;
59-
let mut captures = c
60-
.captured_items(sema.db)
61-
.into_iter()
62-
.map(|it| {
63-
let borrow_kind= match it.kind() {
64-
CaptureKind::SharedRef => "immutable borrow",
65-
CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
66-
CaptureKind::MutableRef => "mutable borrow",
67-
CaptureKind::Move => "move",
68-
};
69-
format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
70-
})
71-
.join("\n");
72-
if captures.trim().is_empty() {
73-
captures = "This closure captures nothing".to_string();
74-
}
75-
let mut res = HoverResult::default();
76-
res.markup = format!(
77-
"```rust\n{}{}\n{}\n```\n\n## Captures\n{}",
78-
c.display_with_id(sema.db),
79-
layout,
80-
c.display_with_impl(sema.db),
81-
captures,
82-
)
83-
.into();
84-
Some(res)
50+
let TypeInfo { original, .. } = sema.type_of_expr(&c.into())?;
51+
closure_ty(sema, config, &TypeInfo { original, adjusted: None })
8552
}
8653

8754
pub(super) fn try_expr(
@@ -522,10 +489,13 @@ pub(super) fn definition(
522489

523490
fn type_info(
524491
sema: &Semantics<'_, RootDatabase>,
525-
_config: &HoverConfig,
526-
original: hir::Type,
527-
adjusted: Option<hir::Type>,
492+
config: &HoverConfig,
493+
ty: TypeInfo,
528494
) -> Option<HoverResult> {
495+
if let Some(res) = closure_ty(sema, config, &ty) {
496+
return Some(res);
497+
};
498+
let TypeInfo { original, adjusted } = ty;
529499
let mut res = HoverResult::default();
530500
let mut targets: Vec<hir::ModuleDef> = Vec::new();
531501
let mut push_new_def = |item: hir::ModuleDef| {
@@ -555,6 +525,69 @@ fn type_info(
555525
Some(res)
556526
}
557527

528+
fn closure_ty(
529+
sema: &Semantics<'_, RootDatabase>,
530+
config: &HoverConfig,
531+
TypeInfo { original, adjusted }: &TypeInfo,
532+
) -> Option<HoverResult> {
533+
let c = original.as_closure()?;
534+
let layout = if config.memory_layout {
535+
original
536+
.layout(sema.db)
537+
.map(|x| format!(" // size = {}, align = {}", x.size.bytes(), x.align.abi.bytes()))
538+
.unwrap_or_default()
539+
} else {
540+
String::default()
541+
};
542+
let mut captures_rendered = c.captured_items(sema.db)
543+
.into_iter()
544+
.map(|it| {
545+
let borrow_kind = match it.kind() {
546+
CaptureKind::SharedRef => "immutable borrow",
547+
CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
548+
CaptureKind::MutableRef => "mutable borrow",
549+
CaptureKind::Move => "move",
550+
};
551+
format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
552+
})
553+
.join("\n");
554+
if captures_rendered.trim().is_empty() {
555+
captures_rendered = "This closure captures nothing".to_string();
556+
}
557+
let mut targets: Vec<hir::ModuleDef> = Vec::new();
558+
let mut push_new_def = |item: hir::ModuleDef| {
559+
if !targets.contains(&item) {
560+
targets.push(item);
561+
}
562+
};
563+
walk_and_push_ty(sema.db, original, &mut push_new_def);
564+
c.capture_types(sema.db).into_iter().for_each(|ty| {
565+
walk_and_push_ty(sema.db, &ty, &mut push_new_def);
566+
});
567+
568+
let adjusted = if let Some(adjusted_ty) = adjusted {
569+
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
570+
format!(
571+
"\nCoerced to: {}",
572+
adjusted_ty.display(sema.db).with_closure_style(hir::ClosureStyle::ImplFn)
573+
)
574+
} else {
575+
String::new()
576+
};
577+
578+
let mut res = HoverResult::default();
579+
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
580+
res.markup = format!(
581+
"```rust\n{}{}\n{}\n```{adjusted}\n\n## Captures\n{}",
582+
c.display_with_id(sema.db),
583+
layout,
584+
c.display_with_impl(sema.db),
585+
captures_rendered,
586+
)
587+
.into();
588+
Some(res)
589+
}
590+
558591
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
559592
let name = attr.name(db);
560593
let desc = format!("#[{name}]");

crates/ide/src/hover/tests.rs

+78
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ fn check_hover_range(ra_fixture: &str, expect: Expect) {
114114
expect.assert_eq(hover.info.markup.as_str())
115115
}
116116

117+
fn check_hover_range_actions(ra_fixture: &str, expect: Expect) {
118+
let (analysis, range) = fixture::range(ra_fixture);
119+
let hover = analysis
120+
.hover(&HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, range)
121+
.unwrap()
122+
.unwrap();
123+
expect.assert_debug_eq(&hover.info.actions);
124+
}
125+
117126
fn check_hover_range_no_results(ra_fixture: &str) {
118127
let (analysis, range) = fixture::range(ra_fixture);
119128
let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap();
@@ -294,6 +303,75 @@ fn main() {
294303
);
295304
}
296305

306+
#[test]
307+
fn hover_ranged_closure() {
308+
check_hover_range(
309+
r#"
310+
//- minicore: fn
311+
struct S;
312+
struct S2;
313+
fn main() {
314+
let x = &S;
315+
let y = ($0|| {x; S2}$0).call();
316+
}
317+
"#,
318+
expect![[r#"
319+
```rust
320+
{closure#0} // size = 8, align = 8
321+
impl FnOnce() -> S2
322+
```
323+
Coerced to: &impl FnOnce() -> S2
324+
325+
## Captures
326+
* `x` by move"#]],
327+
);
328+
check_hover_range_actions(
329+
r#"
330+
//- minicore: fn
331+
struct S;
332+
struct S2;
333+
fn main() {
334+
let x = &S;
335+
let y = ($0|| {x; S2}$0).call();
336+
}
337+
"#,
338+
expect![[r#"
339+
[
340+
GoToType(
341+
[
342+
HoverGotoTypeData {
343+
mod_path: "test::S2",
344+
nav: NavigationTarget {
345+
file_id: FileId(
346+
0,
347+
),
348+
full_range: 10..20,
349+
focus_range: 17..19,
350+
name: "S2",
351+
kind: Struct,
352+
description: "struct S2",
353+
},
354+
},
355+
HoverGotoTypeData {
356+
mod_path: "test::S",
357+
nav: NavigationTarget {
358+
file_id: FileId(
359+
0,
360+
),
361+
full_range: 0..9,
362+
focus_range: 7..8,
363+
name: "S",
364+
kind: Struct,
365+
description: "struct S",
366+
},
367+
},
368+
],
369+
),
370+
]
371+
"#]],
372+
);
373+
}
374+
297375
#[test]
298376
fn hover_shows_long_type_of_an_expression() {
299377
check(

0 commit comments

Comments
 (0)