ide/inlay_hints/
implicit_drop.rs1use 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; }
51 if mir.locals[place.local].ty.as_ref().as_adt().is_none() {
52 continue; }
54 let Some(&binding_idx) = local_to_binding.get(place.local) else {
55 continue; };
57 let range = match terminator.span {
58 MirSpan::ExprId(e) => match source_map.expr_syntax(e) {
59 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 _ => {
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; }
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 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 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}