]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / matches / significant_drop_in_scrutinee.rs
1 use crate::FxHashSet;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::source::{indent_of, snippet};
4 use clippy_utils::{get_attr, is_lint_allowed};
5 use rustc_errors::{Applicability, Diagnostic};
6 use rustc_hir::intravisit::{walk_expr, Visitor};
7 use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
8 use rustc_lint::{LateContext, LintContext};
9 use rustc_middle::ty::subst::GenericArgKind;
10 use rustc_middle::ty::{Ty, TypeAndMut};
11 use rustc_span::Span;
12
13 use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
14
15 pub(super) fn check<'tcx>(
16 cx: &LateContext<'tcx>,
17 expr: &'tcx Expr<'tcx>,
18 scrutinee: &'tcx Expr<'_>,
19 arms: &'tcx [Arm<'_>],
20 source: MatchSource,
21 ) {
22 if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
23 return;
24 }
25
26 if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
27 for found in suggestions {
28 span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
29 set_diagnostic(diag, cx, expr, found);
30 let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
31 diag.span_label(s, "temporary lives until here");
32 for span in has_significant_drop_in_arms(cx, arms) {
33 diag.span_label(span, "another value with significant `Drop` created here");
34 }
35 diag.note("this might lead to deadlocks or other unexpected behavior");
36 });
37 }
38 }
39 }
40
41 fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) {
42 if found.lint_suggestion == LintSuggestion::MoveAndClone {
43 // If our suggestion is to move and clone, then we want to leave it to the user to
44 // decide how to address this lint, since it may be that cloning is inappropriate.
45 // Therefore, we won't to emit a suggestion.
46 return;
47 }
48
49 let original = snippet(cx, found.found_span, "..");
50 let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0));
51
52 let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy {
53 format!("let value = *{original};\n{trailing_indent}")
54 } else if found.is_unit_return_val {
55 // If the return value of the expression to be moved is unit, then we don't need to
56 // capture the result in a temporary -- we can just replace it completely with `()`.
57 format!("{original};\n{trailing_indent}")
58 } else {
59 format!("let value = {original};\n{trailing_indent}")
60 };
61
62 let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly {
63 "try moving the temporary above the match"
64 } else {
65 "try moving the temporary above the match and create a copy"
66 };
67
68 let scrutinee_replacement = if found.is_unit_return_val {
69 "()".to_owned()
70 } else {
71 "value".to_owned()
72 };
73
74 diag.multipart_suggestion(
75 suggestion_message,
76 vec![
77 (expr.span.shrink_to_lo(), replacement),
78 (found.found_span, scrutinee_replacement),
79 ],
80 Applicability::MaybeIncorrect,
81 );
82 }
83
84 /// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that
85 /// may have a surprising lifetime.
86 fn has_significant_drop_in_scrutinee<'tcx, 'a>(
87 cx: &'a LateContext<'tcx>,
88 scrutinee: &'tcx Expr<'tcx>,
89 source: MatchSource,
90 ) -> Option<(Vec<FoundSigDrop>, &'static str)> {
91 let mut helper = SigDropHelper::new(cx);
92 let scrutinee = match (source, &scrutinee.kind) {
93 (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
94 _ => scrutinee,
95 };
96 helper.find_sig_drop(scrutinee).map(|drops| {
97 let message = if source == MatchSource::Normal {
98 "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
99 } else {
100 "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
101 };
102 (drops, message)
103 })
104 }
105
106 struct SigDropChecker<'a, 'tcx> {
107 seen_types: FxHashSet<Ty<'tcx>>,
108 cx: &'a LateContext<'tcx>,
109 }
110
111 impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
112 fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> {
113 SigDropChecker {
114 seen_types: FxHashSet::default(),
115 cx,
116 }
117 }
118
119 fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
120 self.cx.typeck_results().expr_ty(ex)
121 }
122
123 fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
124 !self.seen_types.insert(ty)
125 }
126
127 fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
128 if let Some(adt) = ty.ty_adt_def() {
129 if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
130 return true;
131 }
132 }
133
134 match ty.kind() {
135 rustc_middle::ty::Adt(a, b) => {
136 for f in a.all_fields() {
137 let ty = f.ty(cx.tcx, b);
138 if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
139 return true;
140 }
141 }
142
143 for generic_arg in b.iter() {
144 if let GenericArgKind::Type(ty) = generic_arg.unpack() {
145 if self.has_sig_drop_attr(cx, ty) {
146 return true;
147 }
148 }
149 }
150 false
151 },
152 rustc_middle::ty::Array(ty, _)
153 | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
154 | rustc_middle::ty::Ref(_, ty, _)
155 | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
156 _ => false,
157 }
158 }
159 }
160
161 struct SigDropHelper<'a, 'tcx> {
162 cx: &'a LateContext<'tcx>,
163 is_chain_end: bool,
164 has_significant_drop: bool,
165 current_sig_drop: Option<FoundSigDrop>,
166 sig_drop_spans: Option<Vec<FoundSigDrop>>,
167 special_handling_for_binary_op: bool,
168 sig_drop_checker: SigDropChecker<'a, 'tcx>,
169 }
170
171 #[expect(clippy::enum_variant_names)]
172 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
173 enum LintSuggestion {
174 MoveOnly,
175 MoveAndDerefToCopy,
176 MoveAndClone,
177 }
178
179 #[derive(Clone, Copy)]
180 struct FoundSigDrop {
181 found_span: Span,
182 is_unit_return_val: bool,
183 lint_suggestion: LintSuggestion,
184 }
185
186 impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
187 fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> {
188 SigDropHelper {
189 cx,
190 is_chain_end: true,
191 has_significant_drop: false,
192 current_sig_drop: None,
193 sig_drop_spans: None,
194 special_handling_for_binary_op: false,
195 sig_drop_checker: SigDropChecker::new(cx),
196 }
197 }
198
199 fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option<Vec<FoundSigDrop>> {
200 self.visit_expr(match_expr);
201
202 // If sig drop spans is empty but we found a significant drop, it means that we didn't find
203 // a type that was trivially copyable as we moved up the chain after finding a significant
204 // drop, so move the entire scrutinee.
205 if self.has_significant_drop && self.sig_drop_spans.is_none() {
206 self.try_setting_current_suggestion(match_expr, true);
207 self.move_current_suggestion();
208 }
209
210 self.sig_drop_spans.take()
211 }
212
213 fn replace_current_sig_drop(
214 &mut self,
215 found_span: Span,
216 is_unit_return_val: bool,
217 lint_suggestion: LintSuggestion,
218 ) {
219 self.current_sig_drop.replace(FoundSigDrop {
220 found_span,
221 is_unit_return_val,
222 lint_suggestion,
223 });
224 }
225
226 /// This will try to set the current suggestion (so it can be moved into the suggestions vec
227 /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us
228 /// an opportunity to look for another type in the chain that will be trivially copyable.
229 /// However, if we are at the the end of the chain, we want to accept whatever is there. (The
230 /// suggestion won't actually be output, but the diagnostic message will be output, so the user
231 /// can determine the best way to handle the lint.)
232 fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) {
233 if self.current_sig_drop.is_some() {
234 return;
235 }
236 let ty = self.sig_drop_checker.get_type(expr);
237 if ty.is_ref() {
238 // We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
239 // but let's avoid any chance of an ICE
240 if let Some(TypeAndMut { ty, .. }) = ty.builtin_deref(true) {
241 if ty.is_trivially_pure_clone_copy() {
242 self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy);
243 } else if allow_move_and_clone {
244 self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
245 }
246 }
247 } else if ty.is_trivially_pure_clone_copy() {
248 self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly);
249 } else if allow_move_and_clone {
250 self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
251 }
252 }
253
254 fn move_current_suggestion(&mut self) {
255 if let Some(current) = self.current_sig_drop.take() {
256 self.sig_drop_spans.get_or_insert_with(Vec::new).push(current);
257 }
258 }
259
260 fn visit_exprs_for_binary_ops(
261 &mut self,
262 left: &'tcx Expr<'_>,
263 right: &'tcx Expr<'_>,
264 is_unit_return_val: bool,
265 span: Span,
266 ) {
267 self.special_handling_for_binary_op = true;
268 self.visit_expr(left);
269 self.visit_expr(right);
270
271 // If either side had a significant drop, suggest moving the entire scrutinee to avoid
272 // unnecessary copies and to simplify cases where both sides have significant drops.
273 if self.has_significant_drop {
274 self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly);
275 }
276
277 self.special_handling_for_binary_op = false;
278 }
279 }
280
281 impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
282 fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
283 if !self.is_chain_end
284 && self
285 .sig_drop_checker
286 .has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
287 {
288 self.has_significant_drop = true;
289 return;
290 }
291 self.is_chain_end = false;
292
293 match ex.kind {
294 ExprKind::MethodCall(_, expr, ..) => {
295 self.visit_expr(expr);
296 }
297 ExprKind::Binary(_, left, right) => {
298 self.visit_exprs_for_binary_ops(left, right, false, ex.span);
299 }
300 ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => {
301 self.visit_exprs_for_binary_ops(left, right, true, ex.span);
302 }
303 ExprKind::Tup(exprs) => {
304 for expr in exprs {
305 self.visit_expr(expr);
306 if self.has_significant_drop {
307 // We may have not have set current_sig_drop if all the suggestions were
308 // MoveAndClone, so add this tuple item's full expression in that case.
309 if self.current_sig_drop.is_none() {
310 self.try_setting_current_suggestion(expr, true);
311 }
312
313 // Now we are guaranteed to have something, so add it to the final vec.
314 self.move_current_suggestion();
315 }
316 // Reset `has_significant_drop` after each tuple expression so we can look for
317 // additional cases.
318 self.has_significant_drop = false;
319 }
320 if self.sig_drop_spans.is_some() {
321 self.has_significant_drop = true;
322 }
323 }
324 ExprKind::Box(..) |
325 ExprKind::Array(..) |
326 ExprKind::Call(..) |
327 ExprKind::Unary(..) |
328 ExprKind::If(..) |
329 ExprKind::Match(..) |
330 ExprKind::Field(..) |
331 ExprKind::Index(..) |
332 ExprKind::Ret(..) |
333 ExprKind::Repeat(..) |
334 ExprKind::Yield(..) => walk_expr(self, ex),
335 ExprKind::AddrOf(_, _, _) |
336 ExprKind::Block(_, _) |
337 ExprKind::Break(_, _) |
338 ExprKind::Cast(_, _) |
339 // Don't want to check the closure itself, only invocation, which is covered by MethodCall
340 ExprKind::Closure { .. } |
341 ExprKind::ConstBlock(_) |
342 ExprKind::Continue(_) |
343 ExprKind::DropTemps(_) |
344 ExprKind::Err |
345 ExprKind::InlineAsm(_) |
346 ExprKind::Let(_) |
347 ExprKind::Lit(_) |
348 ExprKind::Loop(_, _, _, _) |
349 ExprKind::Path(_) |
350 ExprKind::Struct(_, _, _) |
351 ExprKind::Type(_, _) => {
352 return;
353 }
354 }
355
356 // Once a significant temporary has been found, we need to go back up at least 1 level to
357 // find the span to extract for replacement, so the temporary gets dropped. However, for
358 // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to
359 // simplify cases where both sides have significant drops.
360 if self.has_significant_drop && !self.special_handling_for_binary_op {
361 self.try_setting_current_suggestion(ex, false);
362 }
363 }
364 }
365
366 struct ArmSigDropHelper<'a, 'tcx> {
367 sig_drop_checker: SigDropChecker<'a, 'tcx>,
368 found_sig_drop_spans: FxHashSet<Span>,
369 }
370
371 impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
372 fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
373 ArmSigDropHelper {
374 sig_drop_checker: SigDropChecker::new(cx),
375 found_sig_drop_spans: FxHashSet::<Span>::default(),
376 }
377 }
378 }
379
380 fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
381 let mut helper = ArmSigDropHelper::new(cx);
382 for arm in arms {
383 helper.visit_expr(arm.body);
384 }
385 helper.found_sig_drop_spans
386 }
387
388 impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
389 fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
390 if self
391 .sig_drop_checker
392 .has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
393 {
394 self.found_sig_drop_spans.insert(ex.span);
395 return;
396 }
397 walk_expr(self, ex);
398 }
399 }