]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | use clippy_utils::source::snippet_opt; | |
3 | use clippy_utils::ty::implements_trait; | |
2b03887a | 4 | use clippy_utils::visitors::for_each_expr; |
add651ee | 5 | use clippy_utils::{binop_traits, eq_expr_value, trait_ref_of_method}; |
2b03887a | 6 | use core::ops::ControlFlow; |
064997fb FG |
7 | use rustc_errors::Applicability; |
8 | use rustc_hir as hir; | |
2b03887a | 9 | use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; |
064997fb FG |
10 | use rustc_lint::LateContext; |
11 | use rustc_middle::mir::FakeReadCause; | |
12 | use rustc_middle::ty::BorrowKind; | |
13 | use rustc_trait_selection::infer::TyCtxtInferExt; | |
064997fb FG |
14 | |
15 | use super::ASSIGN_OP_PATTERN; | |
16 | ||
17 | pub(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 |
101 | fn 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 | ||
133 | fn 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 | } |