ide/inlay_hints/
implicit_drop.rs

1//! Implementation of "implicit drop" inlay hints:
2//! ```ignore
3//! let x = vec![2];
4//! if some_condition() {
5//!     /* drop(x) */return;
6//! }
7//! ```
8use hir::{
9    DefWithBody,
10    db::{DefDatabase as _, HirDatabase as _},
11    mir::{MirSpan, TerminatorKind},
12};
13use ide_db::{FileRange, famous_defs::FamousDefs};
14
15use syntax::{
16    ToSmolStr,
17    ast::{self, AstNode},
18    match_ast,
19};
20
21use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind};
22
23pub(super) fn hints(
24    acc: &mut Vec<InlayHint>,
25    FamousDefs(sema, _): &FamousDefs<'_, '_>,
26    config: &InlayHintsConfig<'_>,
27    display_target: hir::DisplayTarget,
28    node: &ast::Fn,
29) -> Option<()> {
30    if !config.implicit_drop_hints {
31        return None;
32    }
33
34    let def = sema.to_def(node)?;
35    let def: DefWithBody = def.into();
36
37    let def = def.try_into().ok()?;
38    let (hir, source_map) = sema.db.body_with_source_map(def);
39
40    let mir = sema.db.mir_body(def).ok()?;
41
42    let local_to_binding = mir.local_to_binding_map();
43
44    for (_, bb) in mir.basic_blocks.iter() {
45        let terminator = bb.terminator.as_ref()?;
46        if let TerminatorKind::Drop { place, .. } = terminator.kind {
47            if !place.projection.is_empty() {
48                continue; // Ignore complex cases for now
49            }
50            if mir.locals[place.local].ty.as_ref().as_adt().is_none() {
51                continue; // Arguably only ADTs have significant drop impls
52            }
53            let Some(&binding_idx) = local_to_binding.get(place.local) else {
54                continue; // Ignore temporary values
55            };
56            let range = match terminator.span {
57                MirSpan::ExprId(e) => match source_map.expr_syntax(e) {
58                    // don't show inlay hint for macro
59                    Ok(s) if !s.file_id.is_macro() => {
60                        let root = &s.file_syntax(sema.db);
61                        let expr = s.value.to_node(root);
62                        let expr = expr.syntax();
63                        match_ast! {
64                            match expr {
65                                ast::BlockExpr(x) => x.stmt_list().and_then(|x| x.r_curly_token()).map(|x| x.text_range()).unwrap_or_else(|| expr.text_range()),
66                                // make the inlay hint appear after the semicolon if there is
67                                _ => {
68                                    let nearest_semicolon = nearest_token_after_node(expr, syntax::SyntaxKind::SEMICOLON);
69                                    nearest_semicolon.map(|x| x.text_range()).unwrap_or_else(|| expr.text_range())
70                                },
71                            }
72                        }
73                    }
74                    _ => continue,
75                },
76                MirSpan::PatId(p) => match source_map.pat_syntax(p) {
77                    Ok(s) if !s.file_id.is_macro() => s.value.text_range(),
78                    _ => continue,
79                },
80                MirSpan::BindingId(b) => {
81                    match source_map
82                        .patterns_for_binding(b)
83                        .iter()
84                        .find_map(|p| source_map.pat_syntax(*p).ok())
85                    {
86                        Some(s) if !s.file_id.is_macro() => s.value.text_range(),
87                        _ => continue,
88                    }
89                }
90                MirSpan::SelfParam => match source_map.self_param_syntax() {
91                    Some(s) if !s.file_id.is_macro() => s.value.text_range(),
92                    _ => continue,
93                },
94                MirSpan::Unknown => continue,
95            };
96            let binding = &hir[binding_idx];
97            let name = binding.name.display_no_db(display_target.edition).to_smolstr();
98            if name.starts_with("<ra@") {
99                continue; // Ignore desugared variables
100            }
101            let mut label = InlayHintLabel::simple(
102                name,
103                None,
104                config.lazy_location_opt(|| {
105                    source_map
106                        .patterns_for_binding(binding_idx)
107                        .first()
108                        .and_then(|d| source_map.pat_syntax(*d).ok())
109                        .and_then(|d| {
110                            Some(FileRange {
111                                file_id: d.file_id.file_id()?.file_id(sema.db),
112                                range: d.value.text_range(),
113                            })
114                        })
115                }),
116            );
117            label.prepend_str("drop(");
118            label.append_str(")");
119            acc.push(InlayHint {
120                range,
121                position: InlayHintPosition::After,
122                pad_left: true,
123                pad_right: true,
124                kind: InlayKind::Drop,
125                label,
126                text_edit: None,
127                resolve_parent: Some(node.syntax().text_range()),
128            })
129        }
130    }
131
132    Some(())
133}
134
135fn nearest_token_after_node(
136    node: &syntax::SyntaxNode,
137    token_type: syntax::SyntaxKind,
138) -> Option<syntax::SyntaxToken> {
139    node.siblings_with_tokens(syntax::Direction::Next)
140        .filter_map(|it| it.as_token().cloned())
141        .find(|it| it.kind() == token_type)
142}
143
144#[cfg(test)]
145mod tests {
146    use crate::{
147        InlayHintsConfig,
148        inlay_hints::tests::{DISABLED_CONFIG, check_with_config},
149    };
150
151    const ONLY_DROP_CONFIG: InlayHintsConfig<'_> =
152        InlayHintsConfig { implicit_drop_hints: true, ..DISABLED_CONFIG };
153
154    #[test]
155    fn basic() {
156        check_with_config(
157            ONLY_DROP_CONFIG,
158            r#"
159    struct X;
160    fn f() {
161        let x = X;
162        if 2 == 5 {
163            return;
164                //^ drop(x)
165        }
166    }
167  //^ drop(x)
168"#,
169        );
170    }
171
172    #[test]
173    fn no_hint_for_copy_types_and_mutable_references() {
174        // `T: Copy` and `T = &mut U` types do nothing on drop, so we should hide drop inlay hint for them.
175        check_with_config(
176            ONLY_DROP_CONFIG,
177            r#"
178//- minicore: copy, derive
179
180    struct X(i32, i32);
181    #[derive(Clone, Copy)]
182    struct Y(i32, i32);
183    fn f() {
184        let a = 2;
185        let b = a + 4;
186        let mut x = X(a, b);
187        let mut y = Y(a, b);
188        let mx = &mut x;
189        let my = &mut y;
190        let c = a + b;
191    }
192  //^ drop(x)
193"#,
194        );
195    }
196
197    #[test]
198    fn try_operator() {
199        // We currently show drop inlay hint for every `?` operator that may potentially drop something. We probably need to
200        // make it configurable as it doesn't seem very useful.
201        check_with_config(
202            ONLY_DROP_CONFIG,
203            r#"
204//- minicore: copy, try, option
205
206    struct X;
207    fn f() -> Option<()> {
208        let x = X;
209        let t_opt = Some(2);
210        let t = t_opt?;
211                    //^ drop(x)
212        Some(())
213    }
214  //^ drop(x)
215"#,
216        );
217    }
218
219    #[test]
220    fn if_let() {
221        check_with_config(
222            ONLY_DROP_CONFIG,
223            r#"
224    struct X;
225    fn f() {
226        let x = X;
227        if let X = x {
228            let y = X;
229        }
230      //^ drop(y)
231    }
232  //^ drop(x)
233"#,
234        );
235    }
236
237    #[test]
238    fn ignore_inlay_hint_for_macro_call() {
239        check_with_config(
240            ONLY_DROP_CONFIG,
241            r#"
242    struct X;
243
244    macro_rules! my_macro {
245        () => {{
246            let bbb = X;
247            bbb
248        }};
249    }
250
251    fn test() -> X {
252        my_macro!()
253    }
254"#,
255        );
256    }
257}