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