Skip to main content

ide/inlay_hints/
closure_captures.rs

1//! Implementation of "closure captures" inlay hints.
2//!
3//! Tests live in [`bind_pat`][super::bind_pat] module.
4use 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                // force cache the source file, otherwise sema lookup will potentially panic
72                _ = 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}