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