]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/matches/significant_drop_in_scrutinee.rs
Merge commit 'f4850f7292efa33759b4f7f9b7621268979e9914' into clippyup
[rust.git] / 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>(
87     cx: &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 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>(cx: &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 }