ide/inlay_hints/
implicit_drop.rs1use 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; }
54 if mir.locals[place.local].ty.as_ref().as_adt().is_none() {
55 continue; }
57 let Some(&binding_idx) = local_to_binding.get(place.local) else {
58 continue; };
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 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 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 _ => {
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; }
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 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 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}