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