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