1use either::Either;
5use ide_db::famous_defs::FamousDefs;
6use span::Edition;
7use stdx::{TupleExt, never};
8use syntax::ast::{self, AstNode};
9
10use crate::{
11 InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind,
12};
13
14pub(super) fn hints(
15 acc: &mut Vec<InlayHint>,
16 FamousDefs(sema, _): &FamousDefs<'_, '_>,
17 config: &InlayHintsConfig<'_>,
18 expr: Either<ast::ClosureExpr, ast::BlockExpr>,
19 edition: Edition,
20) -> Option<()> {
21 if !config.closure_capture_hints {
22 return None;
23 }
24
25 let (expr, move_token, capture_anchor) = match expr {
26 Either::Left(closure) => {
27 let move_token = closure.move_token();
28 let capture_anchor = closure.param_list()?.pipe_token()?;
29 (closure.into(), move_token, capture_anchor)
30 }
31 Either::Right(block) => {
32 let modifier = block.modifier()?;
33 match modifier {
34 ast::BlockModifier::Async(_)
35 | ast::BlockModifier::Gen(_)
36 | ast::BlockModifier::AsyncGen(_) => (),
37 ast::BlockModifier::Unsafe(_)
38 | ast::BlockModifier::Try { .. }
39 | ast::BlockModifier::Const(_)
40 | ast::BlockModifier::Label(_) => return None,
41 }
42 let move_token = block.move_token();
43 let capture_anchor = block.stmt_list()?.l_curly_token()?;
44 (block.into(), move_token, capture_anchor)
45 }
46 };
47
48 let ty = &sema.type_of_expr(&expr)?.original;
49 let captures = match ty.as_closure() {
50 Some(closure) => closure.captured_items(sema.db),
51 None => ty.as_coroutine()?.captured_items(sema.db),
52 };
53
54 if captures.is_empty() {
55 return None;
56 }
57
58 let (range, label, position, pad_right) = match move_token {
59 Some(token) => {
60 (token.text_range(), InlayHintLabel::default(), InlayHintPosition::After, false)
61 }
62 None => (
63 capture_anchor.text_range(),
64 InlayHintLabel::from("move"),
65 InlayHintPosition::Before,
66 true,
67 ),
68 };
69 let mut hint = InlayHint {
70 range,
71 kind: InlayKind::ClosureCapture,
72 label,
73 text_edit: None,
74 position,
75 pad_left: false,
76 pad_right,
77 resolve_parent: Some(expr.syntax().text_range()),
78 };
79 hint.label.append_str("(");
80 let last = captures.len() - 1;
81 for (idx, capture) in captures.into_iter().enumerate() {
82 let local = capture.local();
83
84 let label = format!(
85 "{}{}",
86 match capture.kind() {
87 hir::CaptureKind::SharedRef => "&",
88 hir::CaptureKind::UniqueSharedRef => "&unique ",
89 hir::CaptureKind::MutableRef => "&mut ",
90 hir::CaptureKind::Move => "",
91 },
92 capture.display_place_source_code(sema.db, edition)
93 );
94 if never!(label.is_empty()) {
95 continue;
96 }
97 hint.label.append_part(InlayHintLabelPart {
98 text: label,
99 linked_location: config.lazy_location_opt(|| {
100 let source = local.primary_source(sema.db);
101
102 _ = sema.parse_or_expand(source.file());
104 source.name().and_then(|name| {
105 name.syntax().original_file_range_opt(sema.db).map(TupleExt::head).map(
106 |frange| ide_db::FileRange {
107 file_id: frange.file_id.file_id(sema.db),
108 range: frange.range,
109 },
110 )
111 })
112 }),
113 tooltip: None,
114 });
115
116 if idx != last {
117 hint.label.append_str(", ");
118 }
119 }
120 hint.label.append_str(")");
121 acc.push(hint);
122 Some(())
123}
124
125#[cfg(test)]
126mod tests {
127 use crate::{
128 InlayHintsConfig,
129 inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
130 };
131
132 #[test]
133 fn all_capture_kinds() {
134 check_with_config(
135 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
136 r#"
137//- minicore: copy, derive, fn
138
139
140#[derive(Copy, Clone)]
141struct Copy;
142
143struct NonCopy;
144
145fn main() {
146 let foo = Copy;
147 let bar = NonCopy;
148 let mut baz = NonCopy;
149 let qux = &mut NonCopy;
150 || {
151 // ^ move(&foo, bar, baz, qux)
152 foo;
153 bar;
154 baz;
155 qux;
156 };
157 || {
158 // ^ move(&foo, &bar, &baz, &qux)
159 &foo;
160 &bar;
161 &baz;
162 &qux;
163 };
164 || {
165 // ^ move(&mut baz)
166 &mut baz;
167 };
168 || {
169 // ^ move(&mut baz, &mut *qux)
170 baz = NonCopy;
171 *qux = NonCopy;
172 };
173}
174"#,
175 );
176 }
177
178 #[test]
179 fn all_capture_kinds_async_closure() {
180 check_with_config(
181 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
182 r#"
183//- minicore: copy, derive, fn, future, async_fn
184
185#[derive(Copy, Clone)]
186struct Copy;
187
188struct NonCopy;
189
190fn main() {
191 let foo = Copy;
192 let bar = NonCopy;
193 let mut baz = NonCopy;
194 let qux = &mut NonCopy;
195 async || {
196 // ^ move(&foo, bar, baz, qux)
197 foo;
198 bar;
199 baz;
200 qux;
201 };
202 async || {
203 // ^ move(&foo, &bar, &baz, &qux)
204 &foo;
205 &bar;
206 &baz;
207 &qux;
208 };
209 async || {
210 // ^ move(&mut baz)
211 &mut baz;
212 };
213 async || {
214 // ^ move(&mut baz, &mut *qux)
215 baz = NonCopy;
216 *qux = NonCopy;
217 };
218}
219
220"#,
221 );
222 }
223
224 #[test]
225 fn all_capture_kinds_async_block() {
226 check_with_config(
227 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
228 r#"
229//- minicore: copy, derive, future
230
231#[derive(Copy, Clone)]
232struct Copy;
233
234struct NonCopy;
235
236fn main() {
237 let foo = Copy;
238 let bar = NonCopy;
239 let mut baz = NonCopy;
240 let qux = &mut NonCopy;
241 async {
242 // ^ move(&foo, bar, baz, qux)
243 foo;
244 bar;
245 baz;
246 qux;
247 };
248 async {
249 // ^ move(&foo, &bar, &baz, &qux)
250 &foo;
251 &bar;
252 &baz;
253 &qux;
254 };
255 async {
256 // ^ move(&mut baz)
257 &mut baz;
258 };
259 async {
260 // ^ move(&mut baz, &mut *qux)
261 baz = NonCopy;
262 *qux = NonCopy;
263 };
264}
265"#,
266 );
267 }
268
269 #[test]
270 fn nested_coroutine_does_not_capture_parent_local() {
271 check_with_config(
272 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
273 r#"
274//- minicore: copy, future
275fn main() {
276 async {
277 let foo = 1;
278 async {
279 // ^ move(&foo)
280 foo;
281 }
282 };
283}
284"#,
285 );
286 }
287
288 #[test]
289 fn coroutine_blocks() {
290 check_with_config(
291 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
292 r#"
293//- minicore: copy, future
294fn main() {
295 let foo = 0;
296 gen {
297 // ^ move(&foo)
298 foo;
299 yield ();
300 };
301 async gen {
302 // ^ move(&foo)
303 foo;
304 yield ();
305 };
306}
307"#,
308 );
309 }
310
311 #[test]
312 fn legacy_coroutine() {
313 check_with_config(
314 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
315 r#"
316//- minicore: copy, coroutine
317fn main() {
318 let foo = 0;
319 let coroutine = #[coroutine] || {
320 // ^ move(&foo)
321 foo;
322 yield ();
323 };
324}
325"#,
326 );
327 }
328
329 #[test]
330 fn move_token() {
331 check_with_config(
332 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
333 r#"
334//- minicore: copy, derive
335fn main() {
336 let foo = u32;
337 move || {
338// ^^^^ (foo)
339 foo;
340 };
341}
342"#,
343 );
344 check_with_config(
345 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
346 r#"
347//- minicore: copy, derive
348fn main() {
349 let foo = u32;
350 async move || {
351 // ^^^^ (foo)
352 foo;
353 };
354}
355"#,
356 );
357 check_with_config(
358 InlayHintsConfig { closure_capture_hints: true, ..DISABLED_CONFIG },
359 r#"
360//- minicore: copy, future
361fn main() {
362 let foo = 0;
363 async move {
364 // ^^^^ (foo)
365 foo;
366 };
367}
368"#,
369 );
370 }
371}