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 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                // force cache the source file, otherwise sema lookup will potentially panic
74                _ = 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}