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};
13 use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
15 pub(super) fn check<'tcx>(
16 cx: &LateContext<'tcx>,
17 expr: &'tcx Expr<'tcx>,
18 scrutinee: &'tcx Expr<'_>,
19 arms: &'tcx [Arm<'_>],
22 if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
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");
35 diag.note("this might lead to deadlocks or other unexpected behavior");
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.
49 let original = snippet(cx, found.found_span, "..");
50 let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0));
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}")
59 format!("let value = {original};\n{trailing_indent}")
62 let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly {
63 "try moving the temporary above the match"
65 "try moving the temporary above the match and create a copy"
68 let scrutinee_replacement = if found.is_unit_return_val {
74 diag.multipart_suggestion(
77 (expr.span.shrink_to_lo(), replacement),
78 (found.found_span, scrutinee_replacement),
80 Applicability::MaybeIncorrect,
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>,
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,
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"
100 "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
106 struct SigDropChecker<'a, 'tcx> {
107 seen_types: FxHashSet<Ty<'tcx>>,
108 cx: &'a LateContext<'tcx>,
111 impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
112 fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> {
114 seen_types: FxHashSet::default(),
119 fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
120 self.cx.typeck_results().expr_ty(ex)
123 fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
124 !self.seen_types.insert(ty)
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 {
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) {
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) {
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),
161 struct SigDropHelper<'a, 'tcx> {
162 cx: &'a LateContext<'tcx>,
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>,
171 #[expect(clippy::enum_variant_names)]
172 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
173 enum LintSuggestion {
179 #[derive(Clone, Copy)]
180 struct FoundSigDrop {
182 is_unit_return_val: bool,
183 lint_suggestion: LintSuggestion,
186 impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
187 fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> {
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),
199 fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option<Vec<FoundSigDrop>> {
200 self.visit_expr(match_expr);
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();
210 self.sig_drop_spans.take()
213 fn replace_current_sig_drop(
216 is_unit_return_val: bool,
217 lint_suggestion: LintSuggestion,
219 self.current_sig_drop.replace(FoundSigDrop {
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() {
236 let ty = self.sig_drop_checker.get_type(expr);
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);
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);
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);
260 fn visit_exprs_for_binary_ops(
262 left: &'tcx Expr<'_>,
263 right: &'tcx Expr<'_>,
264 is_unit_return_val: bool,
267 self.special_handling_for_binary_op = true;
268 self.visit_expr(left);
269 self.visit_expr(right);
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);
277 self.special_handling_for_binary_op = false;
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
286 .has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
288 self.has_significant_drop = true;
291 self.is_chain_end = false;
294 ExprKind::MethodCall(_, expr, ..) => {
295 self.visit_expr(expr);
297 ExprKind::Binary(_, left, right) => {
298 self.visit_exprs_for_binary_ops(left, right, false, ex.span);
300 ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => {
301 self.visit_exprs_for_binary_ops(left, right, true, ex.span);
303 ExprKind::Tup(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);
313 // Now we are guaranteed to have something, so add it to the final vec.
314 self.move_current_suggestion();
316 // Reset `has_significant_drop` after each tuple expression so we can look for
318 self.has_significant_drop = false;
320 if self.sig_drop_spans.is_some() {
321 self.has_significant_drop = true;
325 ExprKind::Array(..) |
327 ExprKind::Unary(..) |
329 ExprKind::Match(..) |
330 ExprKind::Field(..) |
331 ExprKind::Index(..) |
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(_) |
345 ExprKind::InlineAsm(_) |
348 ExprKind::Loop(_, _, _, _) |
350 ExprKind::Struct(_, _, _) |
351 ExprKind::Type(_, _) => {
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);
366 struct ArmSigDropHelper<'a, 'tcx> {
367 sig_drop_checker: SigDropChecker<'a, 'tcx>,
368 found_sig_drop_spans: FxHashSet<Span>,
371 impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
372 fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
374 sig_drop_checker: SigDropChecker::new(cx),
375 found_sig_drop_spans: FxHashSet::<Span>::default(),
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);
383 helper.visit_expr(arm.body);
385 helper.found_sig_drop_spans
388 impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
389 fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
392 .has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
394 self.found_sig_drop_spans.insert(ex.span);