]>
Commit | Line | Data |
---|---|---|
04454e1e FG |
1 | use crate::FxHashSet; |
2 | use clippy_utils::diagnostics::span_lint_and_then; | |
3 | use clippy_utils::get_attr; | |
4 | use clippy_utils::source::{indent_of, snippet}; | |
5 | use rustc_errors::{Applicability, Diagnostic}; | |
6 | use rustc_hir::intravisit::{walk_expr, Visitor}; | |
923072b8 FG |
7 | use rustc_hir::{Expr, ExprKind, MatchSource}; |
8 | use rustc_lint::{LateContext, LintContext}; | |
04454e1e FG |
9 | use rustc_middle::ty::subst::GenericArgKind; |
10 | use rustc_middle::ty::{Ty, TypeAndMut}; | |
04454e1e FG |
11 | use rustc_span::Span; |
12 | ||
923072b8 | 13 | use super::SIGNIFICANT_DROP_IN_SCRUTINEE; |
04454e1e | 14 | |
923072b8 FG |
15 | pub(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 | ||
30 | fn 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 |
75 | fn 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 | ||
91 | struct 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)] |
103 | enum LintSuggestion { | |
104 | MoveOnly, | |
105 | MoveAndDerefToCopy, | |
106 | MoveAndClone, | |
107 | } | |
108 | ||
109 | #[derive(Clone, Copy)] | |
110 | struct FoundSigDrop { | |
111 | found_span: Span, | |
112 | is_unit_return_val: bool, | |
113 | lint_suggestion: LintSuggestion, | |
114 | } | |
115 | ||
116 | impl<'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 | ||
252 | impl<'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 | } |