]> git.proxmox.com Git - rustc.git/blame - 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
CommitLineData
04454e1e
FG
1use crate::FxHashSet;
2use clippy_utils::diagnostics::span_lint_and_then;
04454e1e 3use clippy_utils::source::{indent_of, snippet};
064997fb 4use clippy_utils::{get_attr, is_lint_allowed};
04454e1e
FG
5use rustc_errors::{Applicability, Diagnostic};
6use rustc_hir::intravisit::{walk_expr, Visitor};
064997fb 7use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
923072b8 8use rustc_lint::{LateContext, LintContext};
04454e1e
FG
9use rustc_middle::ty::subst::GenericArgKind;
10use rustc_middle::ty::{Ty, TypeAndMut};
04454e1e
FG
11use rustc_span::Span;
12
923072b8 13use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
04454e1e 14
923072b8
FG
15pub(super) fn check<'tcx>(
16 cx: &LateContext<'tcx>,
17 expr: &'tcx Expr<'tcx>,
18 scrutinee: &'tcx Expr<'_>,
064997fb 19 arms: &'tcx [Arm<'_>],
923072b8
FG
20 source: MatchSource,
21) {
064997fb
FG
22 if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
23 return;
24 }
25
923072b8
FG
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);
064997fb
FG
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");
923072b8 36 });
04454e1e
FG
37 }
38 }
39}
40
41fn 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 {
2b03887a 53 format!("let value = *{original};\n{trailing_indent}")
04454e1e
FG
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 `()`.
2b03887a 57 format!("{original};\n{trailing_indent}")
04454e1e 58 } else {
2b03887a 59 format!("let value = {original};\n{trailing_indent}")
04454e1e
FG
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
923072b8
FG
84/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that
85/// may have a surprising lifetime.
04454e1e
FG
86fn has_significant_drop_in_scrutinee<'tcx, 'a>(
87 cx: &'a LateContext<'tcx>,
923072b8
FG
88 scrutinee: &'tcx Expr<'tcx>,
89 source: MatchSource,
90) -> Option<(Vec<FoundSigDrop>, &'static str)> {
04454e1e 91 let mut helper = SigDropHelper::new(cx);
064997fb
FG
92 let scrutinee = match (source, &scrutinee.kind) {
93 (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
94 _ => scrutinee,
95 };
923072b8
FG
96 helper.find_sig_drop(scrutinee).map(|drops| {
97 let message = if source == MatchSource::Normal {
064997fb 98 "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
923072b8 99 } else {
064997fb 100 "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
923072b8
FG
101 };
102 (drops, message)
103 })
04454e1e
FG
104}
105
064997fb
FG
106struct SigDropChecker<'a, 'tcx> {
107 seen_types: FxHashSet<Ty<'tcx>>,
108 cx: &'a LateContext<'tcx>,
109}
110
111impl<'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
04454e1e
FG
161struct SigDropHelper<'a, 'tcx> {
162 cx: &'a LateContext<'tcx>,
163 is_chain_end: bool,
04454e1e
FG
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,
064997fb 168 sig_drop_checker: SigDropChecker<'a, 'tcx>,
04454e1e
FG
169}
170
923072b8 171#[expect(clippy::enum_variant_names)]
04454e1e
FG
172#[derive(Debug, PartialEq, Eq, Clone, Copy)]
173enum LintSuggestion {
174 MoveOnly,
175 MoveAndDerefToCopy,
176 MoveAndClone,
177}
178
179#[derive(Clone, Copy)]
180struct FoundSigDrop {
181 found_span: Span,
182 is_unit_return_val: bool,
183 lint_suggestion: LintSuggestion,
184}
185
186impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
187 fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> {
188 SigDropHelper {
189 cx,
190 is_chain_end: true,
04454e1e
FG
191 has_significant_drop: false,
192 current_sig_drop: None,
193 sig_drop_spans: None,
194 special_handling_for_binary_op: false,
064997fb 195 sig_drop_checker: SigDropChecker::new(cx),
04454e1e
FG
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
923072b8
FG
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
04454e1e 226 /// This will try to set the current suggestion (so it can be moved into the suggestions vec
923072b8 227 /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us
04454e1e
FG
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 }
064997fb 236 let ty = self.sig_drop_checker.get_type(expr);
04454e1e
FG
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() {
923072b8 242 self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy);
04454e1e 243 } else if allow_move_and_clone {
923072b8 244 self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
04454e1e
FG
245 }
246 }
247 } else if ty.is_trivially_pure_clone_copy() {
923072b8
FG
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);
04454e1e
FG
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
04454e1e
FG
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 {
923072b8 274 self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly);
04454e1e
FG
275 }
276
277 self.special_handling_for_binary_op = false;
278 }
04454e1e
FG
279}
280
281impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
282 fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
064997fb
FG
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 {
04454e1e
FG
288 self.has_significant_drop = true;
289 return;
290 }
291 self.is_chain_end = false;
292
293 match ex.kind {
f2b60f7d 294 ExprKind::MethodCall(_, expr, ..) => {
923072b8 295 self.visit_expr(expr);
04454e1e
FG
296 }
297 ExprKind::Binary(_, left, right) => {
298 self.visit_exprs_for_binary_ops(left, right, false, ex.span);
299 }
923072b8 300 ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => {
04454e1e
FG
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(..) |
923072b8
FG
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(..) |
f2b60f7d 334 ExprKind::Yield(..) => walk_expr(self, ex),
04454e1e 335 ExprKind::AddrOf(_, _, _) |
923072b8
FG
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(_, _) => {
04454e1e
FG
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}
064997fb
FG
365
366struct ArmSigDropHelper<'a, 'tcx> {
367 sig_drop_checker: SigDropChecker<'a, 'tcx>,
368 found_sig_drop_spans: FxHashSet<Span>,
369}
370
371impl<'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
380fn 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
388impl<'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}