1use ide_db::famous_defs::FamousDefs;
5use span::Edition;
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 edition: Edition,
19) -> Option<()> {
20 if !config.closure_capture_hints {
21 return None;
22 }
23 let ty = &sema.type_of_expr(&closure.clone().into())?.original;
24 let c = ty.as_closure()?;
25 let captures = c.captured_items(sema.db);
26
27 if captures.is_empty() {
28 return None;
29 }
30
31 let (range, label, position, pad_right) = match closure.move_token() {
32 Some(t) => (t.text_range(), InlayHintLabel::default(), InlayHintPosition::After, false),
33 None => {
34 let l_pipe = closure.param_list()?.pipe_token()?.text_range();
35 (l_pipe, InlayHintLabel::from("move"), InlayHintPosition::Before, true)
36 }
37 };
38 let mut hint = InlayHint {
39 range,
40 kind: InlayKind::ClosureCapture,
41 label,
42 text_edit: None,
43 position,
44 pad_left: false,
45 pad_right,
46 resolve_parent: Some(closure.syntax().text_range()),
47 };
48 hint.label.append_str("(");
49 let last = captures.len() - 1;
50 for (idx, capture) in captures.into_iter().enumerate() {
51 let local = capture.local();
52
53 let label = format!(
54 "{}{}",
55 match capture.kind() {
56 hir::CaptureKind::SharedRef => "&",
57 hir::CaptureKind::UniqueSharedRef => "&unique ",
58 hir::CaptureKind::MutableRef => "&mut ",
59 hir::CaptureKind::Move => "",
60 },
61 capture.display_place_source_code(sema.db, edition)
62 );
63 if never!(label.is_empty()) {
64 continue;
65 }
66 hint.label.append_part(InlayHintLabelPart {
67 text: label,
68 linked_location: config.lazy_location_opt(|| {
69 let source = local.primary_source(sema.db);
70
71 _ = sema.parse_or_expand(source.file());
73 source.name().and_then(|name| {
74 name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(
75 |frange| ide_db::FileRange {
76 file_id: frange.file_id.file_id(sema.db),
77 range: frange.range,
78 },
79 )
80 })
81 }),
82 tooltip: None,
83 });
84
85 if idx != last {
86 hint.label.append_str(", ");
87 }
88 }
89 hint.label.append_str(")");
90 acc.push(hint);
91 Some(())
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::{
97 InlayHintsConfig,
98 inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
99 };
100
101 #[test]
102 fn all_capture_kinds() {
103 check_with_config(
104 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
105 r#"
106//- minicore: copy, derive, fn
107
108
109#[derive(Copy, Clone)]
110struct Copy;
111
112struct NonCopy;
113
114fn main() {
115 let foo = Copy;
116 let bar = NonCopy;
117 let mut baz = NonCopy;
118 let qux = &mut NonCopy;
119 || {
120 // ^ move(&foo, bar, baz, qux)
121 foo;
122 bar;
123 baz;
124 qux;
125 };
126 || {
127 // ^ move(&foo, &bar, &baz, &qux)
128 &foo;
129 &bar;
130 &baz;
131 &qux;
132 };
133 || {
134 // ^ move(&mut baz)
135 &mut baz;
136 };
137 || {
138 // ^ move(&mut baz, &mut *qux)
139 baz = NonCopy;
140 *qux = NonCopy;
141 };
142}
143"#,
144 );
145 }
146
147 #[test]
148 fn all_capture_kinds_async_closure() {
149 check_with_config(
150 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
151 r#"
152//- minicore: copy, derive, fn, future, async_fn
153
154#[derive(Copy, Clone)]
155struct Copy;
156
157struct NonCopy;
158
159fn main() {
160 let foo = Copy;
161 let bar = NonCopy;
162 let mut baz = NonCopy;
163 let qux = &mut NonCopy;
164 async || {
165 // ^ move(&foo, bar, baz, qux)
166 foo;
167 bar;
168 baz;
169 qux;
170 };
171 async || {
172 // ^ move(&foo, &bar, &baz, &qux)
173 &foo;
174 &bar;
175 &baz;
176 &qux;
177 };
178 async || {
179 // ^ move(&mut baz)
180 &mut baz;
181 };
182 async || {
183 // ^ move(&mut baz, &mut *qux)
184 baz = NonCopy;
185 *qux = NonCopy;
186 };
187}
188
189"#,
190 );
191 }
192
193 #[test]
194 fn move_token() {
195 check_with_config(
196 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
197 r#"
198//- minicore: copy, derive
199fn main() {
200 let foo = u32;
201 move || {
202// ^^^^ (foo)
203 foo;
204 };
205}
206"#,
207 );
208 check_with_config(
209 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
210 r#"
211//- minicore: copy, derive
212fn main() {
213 let foo = u32;
214 async move || {
215 // ^^^^ (foo)
216 foo;
217 };
218}
219"#,
220 );
221 }
222}