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