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