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;
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};
16 use super::ASSIGN_OP_PATTERN;
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<'_>,
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);
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).def_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()]);
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)) {
50 "manual implementation of an assign operation",
52 if let (Some(snip_a), Some(snip_r)) =
53 (snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
58 format!("{} {}= {}", snip_a, op.node.as_str(), snip_r),
59 Applicability::MachineApplicable,
68 let mut visitor = ExprVisitor {
74 walk_expr(&mut visitor, e);
76 if visitor.counter == 1 {
78 if eq_expr_value(cx, assignee, l) {
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() {
89 | hir::BinOpKind::BitXor
90 | hir::BinOpKind::BitAnd
91 | hir::BinOpKind::BitOr => {
101 struct ExprVisitor<'a, 'tcx> {
102 assignee: &'a hir::Expr<'a>,
104 cx: &'a LateContext<'tcx>,
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) {
113 walk_expr(self, expr);
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,
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) {}
136 let mut s = S(hir::HirIdSet::default());
137 cx.tcx.infer_ctxt().enter(|infcx| {
138 let mut v = ExprUseVisitor::new(
141 cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
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,
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) {}
169 let mut s = S(hir::HirIdSet::default());
170 cx.tcx.infer_ctxt().enter(|infcx| {
171 let mut v = ExprUseVisitor::new(
174 cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),