Skip to main content

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