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