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
);