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