]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs
Rollup merge of #103305 - c410-f3r:moar-errors, r=petrochenkov
[rust.git] / src / tools / clippy / clippy_lints / src / operators / assign_op_pattern.rs
1 use clippy_utils::binop_traits;
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::source::snippet_opt;
4 use clippy_utils::ty::implements_trait;
5 use clippy_utils::visitors::for_each_expr;
6 use clippy_utils::{eq_expr_value, trait_ref_of_method};
7 use core::ops::ControlFlow;
8 use if_chain::if_chain;
9 use rustc_errors::Applicability;
10 use rustc_hir as hir;
11 use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
12 use rustc_lint::LateContext;
13 use rustc_middle::mir::FakeReadCause;
14 use rustc_middle::ty::BorrowKind;
15 use rustc_trait_selection::infer::TyCtxtInferExt;
16
17 use super::ASSIGN_OP_PATTERN;
18
19 pub(super) fn check<'tcx>(
20     cx: &LateContext<'tcx>,
21     expr: &'tcx hir::Expr<'_>,
22     assignee: &'tcx hir::Expr<'_>,
23     e: &'tcx hir::Expr<'_>,
24 ) {
25     if let hir::ExprKind::Binary(op, l, r) = &e.kind {
26         let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
27             let ty = cx.typeck_results().expr_ty(assignee);
28             let rty = cx.typeck_results().expr_ty(rhs);
29             if_chain! {
30                 if let Some((_, lang_item)) = binop_traits(op.node);
31                 if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
32                 let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id).def_id;
33                 if trait_ref_of_method(cx, parent_fn)
34                     .map_or(true, |t| t.path.res.def_id() != trait_id);
35                 if implements_trait(cx, ty, trait_id, &[rty.into()]);
36                 then {
37                     // Primitive types execute assign-ops right-to-left. Every other type is left-to-right.
38                     if !(ty.is_primitive() && rty.is_primitive()) {
39                         // TODO: This will have false negatives as it doesn't check if the borrows are
40                         // actually live at the end of their respective expressions.
41                         let mut_borrows = mut_borrows_in_expr(cx, assignee);
42                         let imm_borrows = imm_borrows_in_expr(cx, rhs);
43                         if mut_borrows.iter().any(|id| imm_borrows.contains(id)) {
44                             return;
45                         }
46                     }
47                     span_lint_and_then(
48                         cx,
49                         ASSIGN_OP_PATTERN,
50                         expr.span,
51                         "manual implementation of an assign operation",
52                         |diag| {
53                             if let (Some(snip_a), Some(snip_r)) =
54                                 (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
55                             {
56                                 diag.span_suggestion(
57                                     expr.span,
58                                     "replace it with",
59                                     format!("{snip_a} {}= {snip_r}", op.node.as_str()),
60                                     Applicability::MachineApplicable,
61                                 );
62                             }
63                         },
64                     );
65                 }
66             }
67         };
68
69         let mut found = false;
70         let found_multiple = for_each_expr(e, |e| {
71             if eq_expr_value(cx, assignee, e) {
72                 if found {
73                     return ControlFlow::Break(());
74                 }
75                 found = true;
76             }
77             ControlFlow::Continue(())
78         })
79         .is_some();
80
81         if found && !found_multiple {
82             // a = a op b
83             if eq_expr_value(cx, assignee, l) {
84                 lint(assignee, r);
85             }
86             // a = b commutative_op a
87             // Limited to primitive type as these ops are know to be commutative
88             if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
89                 match op.node {
90                     hir::BinOpKind::Add
91                     | hir::BinOpKind::Mul
92                     | hir::BinOpKind::And
93                     | hir::BinOpKind::Or
94                     | hir::BinOpKind::BitXor
95                     | hir::BinOpKind::BitAnd
96                     | hir::BinOpKind::BitOr => {
97                         lint(assignee, l);
98                     },
99                     _ => {},
100                 }
101             }
102         }
103     }
104 }
105
106 fn imm_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
107     struct S(hir::HirIdSet);
108     impl Delegate<'_> for S {
109         fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) {
110             if matches!(kind, BorrowKind::ImmBorrow | BorrowKind::UniqueImmBorrow) {
111                 self.0.insert(match place.place.base {
112                     PlaceBase::Local(id) => id,
113                     PlaceBase::Upvar(id) => id.var_path.hir_id,
114                     _ => return,
115                 });
116             }
117         }
118
119         fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
120         fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
121         fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {}
122         fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
123     }
124
125     let mut s = S(hir::HirIdSet::default());
126     let infcx = cx.tcx.infer_ctxt().build();
127     let mut v = ExprUseVisitor::new(
128         &mut s,
129         &infcx,
130         cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
131         cx.param_env,
132         cx.typeck_results(),
133     );
134     v.consume_expr(e);
135     s.0
136 }
137
138 fn mut_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
139     struct S(hir::HirIdSet);
140     impl Delegate<'_> for S {
141         fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) {
142             if matches!(kind, BorrowKind::MutBorrow) {
143                 self.0.insert(match place.place.base {
144                     PlaceBase::Local(id) => id,
145                     PlaceBase::Upvar(id) => id.var_path.hir_id,
146                     _ => return,
147                 });
148             }
149         }
150
151         fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
152         fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
153         fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {}
154         fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
155     }
156
157     let mut s = S(hir::HirIdSet::default());
158     let infcx = cx.tcx.infer_ctxt().build();
159     let mut v = ExprUseVisitor::new(
160         &mut s,
161         &infcx,
162         cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
163         cx.param_env,
164         cx.typeck_results(),
165     );
166     v.consume_expr(e);
167     s.0
168 }