2022-06-30 10:50:09 +02:00
|
|
|
use clippy_utils::binop_traits;
|
|
|
|
use clippy_utils::diagnostics::span_lint_and_then;
|
|
|
|
use clippy_utils::source::snippet_opt;
|
|
|
|
use clippy_utils::ty::implements_trait;
|
2022-10-06 09:44:38 +02:00
|
|
|
use clippy_utils::visitors::for_each_expr;
|
2022-06-30 10:50:09 +02:00
|
|
|
use clippy_utils::{eq_expr_value, trait_ref_of_method};
|
2022-10-06 09:44:38 +02:00
|
|
|
use core::ops::ControlFlow;
|
2022-06-30 10:50:09 +02:00
|
|
|
use if_chain::if_chain;
|
|
|
|
use rustc_errors::Applicability;
|
|
|
|
use rustc_hir as hir;
|
2022-10-06 09:44:38 +02:00
|
|
|
use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
2022-06-30 10:50:09 +02:00
|
|
|
use rustc_lint::LateContext;
|
2022-07-28 19:08:22 +02:00
|
|
|
use rustc_middle::mir::FakeReadCause;
|
|
|
|
use rustc_middle::ty::BorrowKind;
|
|
|
|
use rustc_trait_selection::infer::TyCtxtInferExt;
|
2022-06-30 10:50:09 +02:00
|
|
|
|
|
|
|
use super::ASSIGN_OP_PATTERN;
|
|
|
|
|
|
|
|
pub(super) fn check<'tcx>(
|
|
|
|
cx: &LateContext<'tcx>,
|
|
|
|
expr: &'tcx hir::Expr<'_>,
|
|
|
|
assignee: &'tcx hir::Expr<'_>,
|
|
|
|
e: &'tcx hir::Expr<'_>,
|
|
|
|
) {
|
|
|
|
if let hir::ExprKind::Binary(op, l, r) = &e.kind {
|
|
|
|
let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| {
|
|
|
|
let ty = cx.typeck_results().expr_ty(assignee);
|
|
|
|
let rty = cx.typeck_results().expr_ty(rhs);
|
|
|
|
if_chain! {
|
|
|
|
if let Some((_, lang_item)) = binop_traits(op.node);
|
|
|
|
if let Ok(trait_id) = cx.tcx.lang_items().require(lang_item);
|
2022-09-20 14:11:23 +09:00
|
|
|
let parent_fn = cx.tcx.hir().get_parent_item(e.hir_id).def_id;
|
2022-06-30 10:50:09 +02:00
|
|
|
if trait_ref_of_method(cx, parent_fn)
|
|
|
|
.map_or(true, |t| t.path.res.def_id() != trait_id);
|
|
|
|
if implements_trait(cx, ty, trait_id, &[rty.into()]);
|
|
|
|
then {
|
2022-07-28 19:08:22 +02:00
|
|
|
// Primitive types execute assign-ops right-to-left. Every other type is left-to-right.
|
|
|
|
if !(ty.is_primitive() && rty.is_primitive()) {
|
|
|
|
// TODO: This will have false negatives as it doesn't check if the borrows are
|
|
|
|
// actually live at the end of their respective expressions.
|
|
|
|
let mut_borrows = mut_borrows_in_expr(cx, assignee);
|
|
|
|
let imm_borrows = imm_borrows_in_expr(cx, rhs);
|
|
|
|
if mut_borrows.iter().any(|id| imm_borrows.contains(id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2022-06-30 10:50:09 +02:00
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
ASSIGN_OP_PATTERN,
|
|
|
|
expr.span,
|
|
|
|
"manual implementation of an assign operation",
|
|
|
|
|diag| {
|
|
|
|
if let (Some(snip_a), Some(snip_r)) =
|
|
|
|
(snippet_opt(cx, assignee.span), snippet_opt(cx, rhs.span))
|
|
|
|
{
|
|
|
|
diag.span_suggestion(
|
|
|
|
expr.span,
|
|
|
|
"replace it with",
|
2022-10-06 09:44:38 +02:00
|
|
|
format!("{snip_a} {}= {snip_r}", op.node.as_str()),
|
2022-06-30 10:50:09 +02:00
|
|
|
Applicability::MachineApplicable,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-06 09:44:38 +02:00
|
|
|
let mut found = false;
|
|
|
|
let found_multiple = for_each_expr(e, |e| {
|
|
|
|
if eq_expr_value(cx, assignee, e) {
|
|
|
|
if found {
|
|
|
|
return ControlFlow::Break(());
|
|
|
|
}
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
ControlFlow::Continue(())
|
|
|
|
})
|
|
|
|
.is_some();
|
2022-06-30 10:50:09 +02:00
|
|
|
|
2022-10-06 09:44:38 +02:00
|
|
|
if found && !found_multiple {
|
2022-06-30 10:50:09 +02:00
|
|
|
// a = a op b
|
|
|
|
if eq_expr_value(cx, assignee, l) {
|
|
|
|
lint(assignee, r);
|
|
|
|
}
|
|
|
|
// a = b commutative_op a
|
|
|
|
// Limited to primitive type as these ops are know to be commutative
|
|
|
|
if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
|
|
|
|
match op.node {
|
|
|
|
hir::BinOpKind::Add
|
|
|
|
| hir::BinOpKind::Mul
|
|
|
|
| hir::BinOpKind::And
|
|
|
|
| hir::BinOpKind::Or
|
|
|
|
| hir::BinOpKind::BitXor
|
|
|
|
| hir::BinOpKind::BitAnd
|
|
|
|
| hir::BinOpKind::BitOr => {
|
|
|
|
lint(assignee, l);
|
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-28 19:08:22 +02:00
|
|
|
fn imm_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
|
|
|
|
struct S(hir::HirIdSet);
|
|
|
|
impl Delegate<'_> for S {
|
|
|
|
fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) {
|
|
|
|
if matches!(kind, BorrowKind::ImmBorrow | BorrowKind::UniqueImmBorrow) {
|
|
|
|
self.0.insert(match place.place.base {
|
|
|
|
PlaceBase::Local(id) => id,
|
|
|
|
PlaceBase::Upvar(id) => id.var_path.hir_id,
|
|
|
|
_ => return,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
|
|
|
|
fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
|
|
|
|
fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {}
|
|
|
|
fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut s = S(hir::HirIdSet::default());
|
2022-09-19 22:03:59 -05:00
|
|
|
let infcx = cx.tcx.infer_ctxt().build();
|
|
|
|
let mut v = ExprUseVisitor::new(
|
|
|
|
&mut s,
|
|
|
|
&infcx,
|
|
|
|
cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
|
|
|
cx.param_env,
|
|
|
|
cx.typeck_results(),
|
|
|
|
);
|
|
|
|
v.consume_expr(e);
|
2022-07-28 19:08:22 +02:00
|
|
|
s.0
|
|
|
|
}
|
|
|
|
|
|
|
|
fn mut_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
|
|
|
|
struct S(hir::HirIdSet);
|
|
|
|
impl Delegate<'_> for S {
|
|
|
|
fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) {
|
|
|
|
if matches!(kind, BorrowKind::MutBorrow) {
|
|
|
|
self.0.insert(match place.place.base {
|
|
|
|
PlaceBase::Local(id) => id,
|
|
|
|
PlaceBase::Upvar(id) => id.var_path.hir_id,
|
|
|
|
_ => return,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
|
|
|
|
fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
|
|
|
|
fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {}
|
|
|
|
fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut s = S(hir::HirIdSet::default());
|
2022-09-19 22:03:59 -05:00
|
|
|
let infcx = cx.tcx.infer_ctxt().build();
|
|
|
|
let mut v = ExprUseVisitor::new(
|
|
|
|
&mut s,
|
|
|
|
&infcx,
|
|
|
|
cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
|
|
|
|
cx.param_env,
|
|
|
|
cx.typeck_results(),
|
|
|
|
);
|
|
|
|
v.consume_expr(e);
|
2022-07-28 19:08:22 +02:00
|
|
|
s.0
|
|
|
|
}
|