ide/inlay_hints/
closure_captures.rs1use ide_db::famous_defs::FamousDefs;
5use ide_db::text_edit::{TextRange, TextSize};
6use stdx::{TupleExt, never};
7use syntax::ast::{self, AstNode};
8
9use crate::{
10 InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
11};
12
13pub(super) fn hints(
14 acc: &mut Vec<InlayHint>,
15 FamousDefs(sema, _): &FamousDefs<'_, '_>,
16 config: &InlayHintsConfig<'_>,
17 closure: ast::ClosureExpr,
18) -> Option<()> {
19 if !config.closure_capture_hints {
20 return None;
21 }
22 let ty = &sema.type_of_expr(&closure.clone().into())?.original;
23 let c = ty.as_closure()?;
24 let captures = c.captured_items(sema.db);
25
26 if captures.is_empty() {
27 return None;
28 }
29
30 let (range, label) = match closure.move_token() {
31 Some(t) => (t.text_range(), InlayHintLabel::default()),
32 None => {
33 let prev_token = closure.syntax().first_token()?.prev_token()?.text_range();
34 (
35 TextRange::new(prev_token.end() - TextSize::from(1), prev_token.end()),
36 InlayHintLabel::from("move"),
37 )
38 }
39 };
40 let mut hint = InlayHint {
41 range,
42 kind: InlayKind::ClosureCapture,
43 label,
44 text_edit: None,
45 position: InlayHintPosition::After,
46 pad_left: false,
47 pad_right: true,
48 resolve_parent: Some(closure.syntax().text_range()),
49 };
50 hint.label.append_str("(");
51 let last = captures.len() - 1;
52 for (idx, capture) in captures.into_iter().enumerate() {
53 let local = capture.local();
54
55 let label = format!(
56 "{}{}",
57 match capture.kind() {
58 hir::CaptureKind::SharedRef => "&",
59 hir::CaptureKind::UniqueSharedRef => "&unique ",
60 hir::CaptureKind::MutableRef => "&mut ",
61 hir::CaptureKind::Move => "",
62 },
63 capture.display_place(sema.db)
64 );
65 if never!(label.is_empty()) {
66 continue;
67 }
68 hint.label.append_part(InlayHintLabelPart {
69 text: label,
70 linked_location: config.lazy_location_opt(|| {
71 let source = local.primary_source(sema.db);
72
73 _ = sema.parse_or_expand(source.file());
75 source.name().and_then(|name| {
76 name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(
77 |frange| ide_db::FileRange {
78 file_id: frange.file_id.file_id(sema.db),
79 range: frange.range,
80 },
81 )
82 })
83 }),
84 tooltip: None,
85 });
86
87 if idx != last {
88 hint.label.append_str(", ");
89 }
90 }
91 hint.label.append_str(")");
92 acc.push(hint);
93 Some(())
94}
95
96#[cfg(test)]
97mod tests {
98 use crate::{
99 InlayHintsConfig,
100 inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
101 };
102
103 #[test]
104 fn all_capture_kinds() {
105 check_with_config(
106 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
107 r#"
108//- minicore: copy, derive
109
110
111#[derive(Copy, Clone)]
112struct Copy;
113
114struct NonCopy;
115
116fn main() {
117 let foo = Copy;
118 let bar = NonCopy;
119 let mut baz = NonCopy;
120 let qux = &mut NonCopy;
121 || {
122// ^ move(&foo, bar, baz, qux)
123 foo;
124 bar;
125 baz;
126 qux;
127 };
128 || {
129// ^ move(&foo, &bar, &baz, &qux)
130 &foo;
131 &bar;
132 &baz;
133 &qux;
134 };
135 || {
136// ^ move(&mut baz)
137 &mut baz;
138 };
139 || {
140// ^ move(&mut baz, &mut *qux)
141 baz = NonCopy;
142 *qux = NonCopy;
143 };
144}
145"#,
146 );
147 }
148
149 #[test]
150 fn move_token() {
151 check_with_config(
152 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
153 r#"
154//- minicore: copy, derive
155fn main() {
156 let foo = u32;
157 move || {
158// ^^^^ (foo)
159 foo;
160 };
161}
162"#,
163 );
164 }
165}