Skip to content

Commit 2403dd1

Browse files
committed
feat: Closure capture inlay hints
1 parent 0dd94d3 commit 2403dd1

File tree

10 files changed

+265
-14
lines changed

10 files changed

+265
-14
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl HirPlace {
148148
}
149149

150150
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
151-
pub(crate) enum CaptureKind {
151+
pub enum CaptureKind {
152152
ByRef(BorrowKind),
153153
ByValue,
154154
}
@@ -166,6 +166,10 @@ impl CapturedItem {
166166
self.place.local
167167
}
168168

169+
pub fn kind(&self) -> CaptureKind {
170+
self.kind
171+
}
172+
169173
pub fn display_kind(&self) -> &'static str {
170174
match self.kind {
171175
CaptureKind::ByRef(k) => match k {

crates/hir-ty/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ pub use autoderef::autoderef;
6161
pub use builder::{ParamKind, TyBuilder};
6262
pub use chalk_ext::*;
6363
pub use infer::{
64-
closure::CapturedItem, could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode,
65-
InferenceDiagnostic, InferenceResult, OverloadedDeref, PointerCast,
64+
closure::{CaptureKind, CapturedItem},
65+
could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
66+
InferenceResult, OverloadedDeref, PointerCast,
6667
};
6768
pub use interner::Interner;
6869
pub use lower::{

crates/hir/src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2611,6 +2611,10 @@ impl LocalSource {
26112611
self.source.file_id.original_file(db.upcast())
26122612
}
26132613

2614+
pub fn file(&self) -> HirFileId {
2615+
self.source.file_id
2616+
}
2617+
26142618
pub fn name(&self) -> Option<ast::Name> {
26152619
self.source.value.name()
26162620
}
@@ -3232,6 +3236,21 @@ impl ClosureCapture {
32323236
Local { parent: self.owner, binding_id: self.capture.local() }
32333237
}
32343238

3239+
pub fn kind(&self) -> CaptureKind {
3240+
match self.capture.kind() {
3241+
hir_ty::CaptureKind::ByRef(
3242+
hir_ty::mir::BorrowKind::Shallow | hir_ty::mir::BorrowKind::Shared,
3243+
) => CaptureKind::SharedRef,
3244+
hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Unique) => {
3245+
CaptureKind::UniqueSharedRef
3246+
}
3247+
hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Mut { .. }) => {
3248+
CaptureKind::MutableRef
3249+
}
3250+
hir_ty::CaptureKind::ByValue => CaptureKind::Move,
3251+
}
3252+
}
3253+
32353254
pub fn display_kind(&self) -> &'static str {
32363255
self.capture.display_kind()
32373256
}
@@ -3241,6 +3260,13 @@ impl ClosureCapture {
32413260
}
32423261
}
32433262

3263+
pub enum CaptureKind {
3264+
SharedRef,
3265+
UniqueSharedRef,
3266+
MutableRef,
3267+
Move,
3268+
}
3269+
32443270
#[derive(Clone, PartialEq, Eq, Debug)]
32453271
pub struct Type {
32463272
env: Arc<TraitEnvironment>,

crates/ide/src/inlay_hints.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@ use text_edit::TextEdit;
2020

2121
use crate::{navigation_target::TryToNav, FileId};
2222

23-
mod closing_brace;
24-
mod implicit_static;
25-
mod fn_lifetime_fn;
26-
mod closure_ret;
2723
mod adjustment;
28-
mod chaining;
29-
mod param_name;
30-
mod binding_mode;
3124
mod bind_pat;
25+
mod binding_mode;
26+
mod chaining;
27+
mod closing_brace;
28+
mod closure_ret;
29+
mod closure_captures;
3230
mod discriminant;
31+
mod fn_lifetime_fn;
32+
mod implicit_static;
33+
mod param_name;
3334

3435
#[derive(Clone, Debug, PartialEq, Eq)]
3536
pub struct InlayHintsConfig {
@@ -42,6 +43,7 @@ pub struct InlayHintsConfig {
4243
pub adjustment_hints_mode: AdjustmentHintsMode,
4344
pub adjustment_hints_hide_outside_unsafe: bool,
4445
pub closure_return_type_hints: ClosureReturnTypeHints,
46+
pub closure_capture_hints: bool,
4547
pub binding_mode_hints: bool,
4648
pub lifetime_elision_hints: LifetimeElisionHints,
4749
pub param_names_for_lifetime_elision_hints: bool,
@@ -88,6 +90,8 @@ pub enum AdjustmentHintsMode {
8890
PreferPostfix,
8991
}
9092

93+
// FIXME: Clean up this mess, the kinds are mainly used for setting different rendering properties in the lsp layer
94+
// We should probably turns this into such a property holding struct. Or clean this up in some other form.
9195
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
9296
pub enum InlayKind {
9397
BindingMode,
@@ -98,6 +102,7 @@ pub enum InlayKind {
98102
Adjustment,
99103
AdjustmentPostfix,
100104
Lifetime,
105+
ClosureCapture,
101106
Parameter,
102107
Type,
103108
Discriminant,
@@ -444,10 +449,10 @@ fn hints(
444449
ast::Expr::MethodCallExpr(it) => {
445450
param_name::hints(hints, sema, config, ast::Expr::from(it))
446451
}
447-
ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, famous_defs, config, file_id, it),
448-
// We could show reborrows for all expressions, but usually that is just noise to the user
449-
// and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
450-
// ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
452+
ast::Expr::ClosureExpr(it) => {
453+
closure_captures::hints(hints, famous_defs, config, file_id, it.clone());
454+
closure_ret::hints(hints, famous_defs, config, file_id, it)
455+
},
451456
_ => None,
452457
}
453458
},
@@ -535,6 +540,7 @@ mod tests {
535540
chaining_hints: false,
536541
lifetime_elision_hints: LifetimeElisionHints::Never,
537542
closure_return_type_hints: ClosureReturnTypeHints::Never,
543+
closure_capture_hints: false,
538544
adjustment_hints: AdjustmentHints::Never,
539545
adjustment_hints_mode: AdjustmentHintsMode::Prefix,
540546
adjustment_hints_hide_outside_unsafe: false,
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
//! Implementation of "closure return type" inlay hints.
2+
//!
3+
//! Tests live in [`bind_pat`][super::bind_pat] module.
4+
use ide_db::{base_db::FileId, famous_defs::FamousDefs};
5+
use syntax::ast::{self, AstNode};
6+
use text_edit::{TextRange, TextSize};
7+
8+
use crate::{InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind};
9+
10+
pub(super) fn hints(
11+
acc: &mut Vec<InlayHint>,
12+
FamousDefs(sema, _): &FamousDefs<'_, '_>,
13+
config: &InlayHintsConfig,
14+
_file_id: FileId,
15+
closure: ast::ClosureExpr,
16+
) -> Option<()> {
17+
if !config.closure_capture_hints {
18+
return None;
19+
}
20+
let ty = &sema.type_of_expr(&closure.clone().into())?.original;
21+
let c = ty.as_closure()?;
22+
let captures = c.captured_items(sema.db);
23+
24+
if captures.is_empty() {
25+
return None;
26+
}
27+
28+
let move_kw_range = match closure.move_token() {
29+
Some(t) => t.text_range(),
30+
None => {
31+
let range = closure.syntax().first_token()?.prev_token()?.text_range();
32+
let range = TextRange::new(range.end() - TextSize::from(1), range.end());
33+
acc.push(InlayHint {
34+
range,
35+
kind: InlayKind::ClosureCapture,
36+
label: InlayHintLabel::simple("move", None, None),
37+
text_edit: None,
38+
});
39+
range
40+
}
41+
};
42+
acc.push(InlayHint {
43+
range: move_kw_range,
44+
kind: InlayKind::ClosureCapture,
45+
label: InlayHintLabel::from("("),
46+
text_edit: None,
47+
});
48+
let last = captures.len() - 1;
49+
for (idx, capture) in captures.into_iter().enumerate() {
50+
let local = capture.local();
51+
let source = local.primary_source(sema.db);
52+
53+
// force cache the source file, otherwise sema lookup will potentially panic
54+
_ = sema.parse_or_expand(source.file());
55+
56+
acc.push(InlayHint {
57+
range: move_kw_range,
58+
kind: InlayKind::ClosureCapture,
59+
label: InlayHintLabel::simple(
60+
format!(
61+
"{}{}",
62+
match capture.kind() {
63+
hir::CaptureKind::SharedRef => "&",
64+
hir::CaptureKind::UniqueSharedRef => "&unique ",
65+
hir::CaptureKind::MutableRef => "&mut ",
66+
hir::CaptureKind::Move => "",
67+
},
68+
local.name(sema.db)
69+
),
70+
None,
71+
source.name().and_then(|name| sema.original_range_opt(name.syntax())),
72+
),
73+
text_edit: None,
74+
});
75+
76+
if idx != last {
77+
acc.push(InlayHint {
78+
range: move_kw_range,
79+
kind: InlayKind::ClosureCapture,
80+
label: InlayHintLabel::simple(", ", None, None),
81+
text_edit: None,
82+
});
83+
}
84+
}
85+
acc.push(InlayHint {
86+
range: move_kw_range,
87+
kind: InlayKind::ClosureCapture,
88+
label: InlayHintLabel::from(")"),
89+
text_edit: None,
90+
});
91+
92+
Some(())
93+
}
94+
95+
#[cfg(test)]
96+
mod tests {
97+
use crate::{
98+
inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
99+
InlayHintsConfig,
100+
};
101+
102+
#[test]
103+
fn all_capture_kinds() {
104+
check_with_config(
105+
InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
106+
r#"
107+
//- minicore: copy, derive
108+
109+
110+
#[derive(Copy, Clone)]
111+
struct Copy;
112+
113+
struct NonCopy;
114+
115+
fn main() {
116+
let foo = Copy;
117+
let bar = NonCopy;
118+
let mut baz = NonCopy;
119+
let qux = &mut NonCopy;
120+
// FIXME: &foo should be foo
121+
|| {
122+
// ^ move
123+
// ^ (
124+
// ^ &foo
125+
// ^ ,
126+
// ^ bar
127+
// ^ ,
128+
// ^ baz
129+
// ^ ,
130+
// ^ qux
131+
// ^ )
132+
foo;
133+
bar;
134+
baz;
135+
qux;
136+
};
137+
|| {
138+
// ^ move
139+
// ^ (
140+
// ^ &foo
141+
// ^ ,
142+
// ^ &bar
143+
// ^ ,
144+
// ^ &baz
145+
// ^ ,
146+
// ^ &qux
147+
// ^ )
148+
&foo;
149+
&bar;
150+
&baz;
151+
&qux;
152+
};
153+
|| {
154+
// ^ move
155+
// ^ (
156+
// ^ &mut baz
157+
// ^ )
158+
&mut baz;
159+
};
160+
// FIXME: &mut qux should be &unique qux
161+
|| {
162+
// ^ move
163+
// ^ (
164+
// ^ &mut baz
165+
// ^ ,
166+
// ^ &mut qux
167+
// ^ )
168+
baz = NonCopy;
169+
*qux = NonCopy;
170+
};
171+
}
172+
"#,
173+
);
174+
}
175+
176+
#[test]
177+
fn move_token() {
178+
check_with_config(
179+
InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
180+
r#"
181+
//- minicore: copy, derive
182+
fn main() {
183+
let foo = u32;
184+
move || {
185+
// ^^^^ (
186+
// ^^^^ foo
187+
// ^^^^ )
188+
foo;
189+
};
190+
}
191+
"#,
192+
);
193+
}
194+
}

crates/ide/src/static_index.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ impl StaticIndex<'_> {
122122
param_names_for_lifetime_elision_hints: false,
123123
binding_mode_hints: false,
124124
max_length: Some(25),
125+
closure_capture_hints: false,
125126
closing_brace_hints_min_lines: Some(25),
126127
},
127128
file_id,

crates/rust-analyzer/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ config_data! {
338338
/// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
339339
/// to always show them).
340340
inlayHints_closingBraceHints_minLines: usize = "25",
341+
/// Whether to show inlay hints for closure captures.
342+
inlayHints_closureCaptureHints_enable: bool = "false",
341343
/// Whether to show inlay type hints for return types of closures.
342344
inlayHints_closureReturnTypeHints_enable: ClosureReturnTypeHintsDef = "\"never\"",
343345
/// Closure notation in type and chaining inlay hints.
@@ -1312,6 +1314,7 @@ impl Config {
13121314
ClosureStyle::WithId => hir::ClosureStyle::ClosureWithId,
13131315
ClosureStyle::Hide => hir::ClosureStyle::Hide,
13141316
},
1317+
closure_capture_hints: self.data.inlayHints_closureCaptureHints_enable,
13151318
adjustment_hints: match self.data.inlayHints_expressionAdjustmentHints_enable {
13161319
AdjustmentHintsDef::Always => ide::AdjustmentHints::Always,
13171320
AdjustmentHintsDef::Never => match self.data.inlayHints_reborrowHints_enable {

0 commit comments

Comments
 (0)