1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::source
::snippet_opt
;
3 use clippy_utils
::ty
::implements_trait
;
4 use clippy_utils
::{binop_traits, sugg}
;
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, NestedVisitorMap, Visitor}
;
10 use rustc_lint
::{LateContext, LateLintPass}
;
11 use rustc_middle
::hir
::map
::Map
;
12 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
14 declare_clippy_lint
! {
16 /// Checks for `a = a op b` or `a = b commutative_op a`
19 /// ### Why is this bad?
20 /// These can be written as the shorter `a op= b`.
22 /// ### Known problems
23 /// While forbidden by the spec, `OpAssign` traits may have
24 /// implementations that differ from the regular `Op` impl.
37 pub ASSIGN_OP_PATTERN
,
39 "assigning the result of an operation on a variable to that same variable"
42 declare_clippy_lint
! {
44 /// Checks for `a op= a op b` or `a op= b op a` patterns.
46 /// ### Why is this bad?
47 /// Most likely these are bugs where one meant to write `a
50 /// ### Known problems
51 /// Clippy cannot know for sure if `a op= a op b` should have
52 /// been `a = a op a op b` or `a = a op b`/`a op= b`. Therefore, it suggests both.
53 /// If `a op= a op b` is really the correct behaviour it should be
54 /// written as `a = a op a op b` as it's less confusing.
63 pub MISREFACTORED_ASSIGN_OP
,
65 "having a variable on both sides of an assign op"
68 declare_lint_pass
!(AssignOps
=> [ASSIGN_OP_PATTERN
, MISREFACTORED_ASSIGN_OP
]);
70 impl<'tcx
> LateLintPass
<'tcx
> for AssignOps
{
71 #[allow(clippy::too_many_lines)]
72 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'_
>) {
74 hir
::ExprKind
::AssignOp(op
, lhs
, rhs
) => {
75 if let hir
::ExprKind
::Binary(binop
, l
, r
) = &rhs
.kind
{
76 if op
.node
!= binop
.node
{
80 if eq_expr_value(cx
, lhs
, l
) {
81 lint_misrefactored_assign_op(cx
, expr
, *op
, rhs
, lhs
, r
);
83 // lhs op= l commutative_op r
84 if is_commutative(op
.node
) && eq_expr_value(cx
, lhs
, r
) {
85 lint_misrefactored_assign_op(cx
, expr
, *op
, rhs
, lhs
, l
);
89 hir
::ExprKind
::Assign(assignee
, e
, _
) => {
90 if let hir
::ExprKind
::Binary(op
, l
, r
) = &e
.kind
{
91 let lint
= |assignee
: &hir
::Expr
<'_
>, rhs
: &hir
::Expr
<'_
>| {
92 let ty
= cx
.typeck_results().expr_ty(assignee
);
93 let rty
= cx
.typeck_results().expr_ty(rhs
);
95 if let Some((_
, lang_item
)) = binop_traits(op
.node
);
96 if let Ok(trait_id
) = cx
.tcx
.lang_items().require(lang_item
);
97 let parent_fn
= cx
.tcx
.hir().get_parent_item(e
.hir_id
);
98 if trait_ref_of_method(cx
, parent_fn
)
99 .map_or(true, |t
| t
.path
.res
.def_id() != trait_id
);
100 if implements_trait(cx
, ty
, trait_id
, &[rty
.into()]);
106 "manual implementation of an assign operation",
108 if let (Some(snip_a
), Some(snip_r
)) =
109 (snippet_opt(cx
, assignee
.span
), snippet_opt(cx
, rhs
.span
))
111 diag
.span_suggestion(
114 format
!("{} {}= {}", snip_a
, op
.node
.as_str(), snip_r
),
115 Applicability
::MachineApplicable
,
124 let mut visitor
= ExprVisitor
{
130 walk_expr(&mut visitor
, e
);
132 if visitor
.counter
== 1 {
134 if eq_expr_value(cx
, assignee
, l
) {
137 // a = b commutative_op a
138 // Limited to primitive type as these ops are know to be commutative
139 if eq_expr_value(cx
, assignee
, r
) && cx
.typeck_results().expr_ty(assignee
).is_primitive_ty() {
142 | hir
::BinOpKind
::Mul
143 | hir
::BinOpKind
::And
145 | hir
::BinOpKind
::BitXor
146 | hir
::BinOpKind
::BitAnd
147 | hir
::BinOpKind
::BitOr
=> {
161 fn lint_misrefactored_assign_op(
162 cx
: &LateContext
<'_
>,
163 expr
: &hir
::Expr
<'_
>,
166 assignee
: &hir
::Expr
<'_
>,
167 rhs_other
: &hir
::Expr
<'_
>,
171 MISREFACTORED_ASSIGN_OP
,
173 "variable appears on both sides of an assignment operation",
175 if let (Some(snip_a
), Some(snip_r
)) = (snippet_opt(cx
, assignee
.span
), snippet_opt(cx
, rhs_other
.span
)) {
176 let a
= &sugg
::Sugg
::hir(cx
, assignee
, "..");
177 let r
= &sugg
::Sugg
::hir(cx
, rhs
, "..");
178 let long
= format
!("{} = {}", snip_a
, sugg
::make_binop(op
.node
.into(), a
, r
));
179 diag
.span_suggestion(
182 "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with",
189 format
!("{} {}= {}", snip_a
, op
.node
.as_str(), snip_r
),
190 Applicability
::MaybeIncorrect
,
192 diag
.span_suggestion(
196 Applicability
::MaybeIncorrect
, // snippet
204 fn is_commutative(op
: hir
::BinOpKind
) -> bool
{
205 use rustc_hir
::BinOpKind
::{
206 Add
, And
, BitAnd
, BitOr
, BitXor
, Div
, Eq
, Ge
, Gt
, Le
, Lt
, Mul
, Ne
, Or
, Rem
, Shl
, Shr
, Sub
,
209 Add
| Mul
| And
| Or
| BitXor
| BitAnd
| BitOr
| Eq
| Ne
=> true,
210 Sub
| Div
| Rem
| Shl
| Shr
| Lt
| Le
| Ge
| Gt
=> false,
214 struct ExprVisitor
<'a
, 'tcx
> {
215 assignee
: &'a hir
::Expr
<'a
>,
217 cx
: &'a LateContext
<'tcx
>,
220 impl<'a
, 'tcx
> Visitor
<'tcx
> for ExprVisitor
<'a
, 'tcx
> {
221 type Map
= Map
<'tcx
>;
223 fn visit_expr(&mut self, expr
: &'tcx hir
::Expr
<'_
>) {
224 if eq_expr_value(self.cx
, self.assignee
, expr
) {
228 walk_expr(self, expr
);
230 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
231 NestedVisitorMap
::None