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