Tighten up assignment operator representations.

In the AST, currently we use `BinOpKind` within `ExprKind::AssignOp` and
`AssocOp::AssignOp`, even though this allows some nonsensical
combinations. E.g. there is no `&&=` operator. Likewise for HIR and
THIR.

This commit introduces `AssignOpKind` which only includes the ten
assignable operators, and uses it in `ExprKind::AssignOp` and
`AssocOp::AssignOp`. (And does similar things for `hir::ExprKind` and
`thir::ExprKind`.) This avoids the possibility of nonsensical
combinations, as seen by the removal of the `bug!` case in
`lang_item_for_binop`.

The commit is mostly plumbing, including:
- Adds an `impl From<AssignOpKind> for BinOpKind` (AST) and `impl
  From<AssignOp> for BinOp` (MIR/THIR).
- `BinOpCategory` can now be created from both `BinOpKind` and
  `AssignOpKind`.
- Replaces the `IsAssign` type with `Op`, which has more information and
  a few methods.
- `suggest_swapping_lhs_and_rhs`: moves the condition to the call site,
  it's easier that way.
- `check_expr_inner`: had to factor out some code into a separate
  method.

I'm on the fence about whether avoiding the nonsensical combinations is
worth the extra code.
This commit is contained in:
Nicholas Nethercote 2024-12-20 10:15:05 +11:00
parent ac8ccf09b4
commit ddcb370bc6
26 changed files with 391 additions and 239 deletions

View File

@ -981,6 +981,75 @@ impl BinOpKind {
pub type BinOp = Spanned<BinOpKind>;
// Sometimes `BinOpKind` and `AssignOpKind` need the same treatment. The
// operations covered by `AssignOpKind` are a subset of those covered by
// `BinOpKind`, so it makes sense to convert `AssignOpKind` to `BinOpKind`.
impl From<AssignOpKind> for BinOpKind {
fn from(op: AssignOpKind) -> BinOpKind {
match op {
AssignOpKind::AddAssign => BinOpKind::Add,
AssignOpKind::SubAssign => BinOpKind::Sub,
AssignOpKind::MulAssign => BinOpKind::Mul,
AssignOpKind::DivAssign => BinOpKind::Div,
AssignOpKind::RemAssign => BinOpKind::Rem,
AssignOpKind::BitXorAssign => BinOpKind::BitXor,
AssignOpKind::BitAndAssign => BinOpKind::BitAnd,
AssignOpKind::BitOrAssign => BinOpKind::BitOr,
AssignOpKind::ShlAssign => BinOpKind::Shl,
AssignOpKind::ShrAssign => BinOpKind::Shr,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Encodable, Decodable, HashStable_Generic)]
pub enum AssignOpKind {
/// The `+=` operator (addition)
AddAssign,
/// The `-=` operator (subtraction)
SubAssign,
/// The `*=` operator (multiplication)
MulAssign,
/// The `/=` operator (division)
DivAssign,
/// The `%=` operator (modulus)
RemAssign,
/// The `^=` operator (bitwise xor)
BitXorAssign,
/// The `&=` operator (bitwise and)
BitAndAssign,
/// The `|=` operator (bitwise or)
BitOrAssign,
/// The `<<=` operator (shift left)
ShlAssign,
/// The `>>=` operator (shift right)
ShrAssign,
}
impl AssignOpKind {
pub fn as_str(&self) -> &'static str {
use AssignOpKind::*;
match self {
AddAssign => "+=",
SubAssign => "-=",
MulAssign => "*=",
DivAssign => "/=",
RemAssign => "%=",
BitXorAssign => "^=",
BitAndAssign => "&=",
BitOrAssign => "|=",
ShlAssign => "<<=",
ShrAssign => ">>=",
}
}
/// AssignOps are always by value.
pub fn is_by_value(self) -> bool {
true
}
}
pub type AssignOp = Spanned<AssignOpKind>;
/// Unary operator.
///
/// Note that `&data` is not an operator, it's an `AddrOf` expression.
@ -1593,7 +1662,7 @@ pub enum ExprKind {
/// An assignment with an operator.
///
/// E.g., `a += 1`.
AssignOp(BinOp, P<Expr>, P<Expr>),
AssignOp(AssignOp, P<Expr>, P<Expr>),
/// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct field.
Field(P<Expr>, Ident),
/// An indexing operation (e.g., `foo[2]`).

View File

@ -1,6 +1,6 @@
use rustc_span::kw;
use crate::ast::{self, BinOpKind, RangeLimits};
use crate::ast::{self, AssignOpKind, BinOpKind, RangeLimits};
use crate::token::{self, Token};
/// Associative operator.
@ -9,7 +9,7 @@ pub enum AssocOp {
/// A binary op.
Binary(BinOpKind),
/// `?=` where ? is one of the assignable BinOps
AssignOp(BinOpKind),
AssignOp(AssignOpKind),
/// `=`
Assign,
/// `as`
@ -44,16 +44,16 @@ impl AssocOp {
token::Or => Some(Binary(BinOpKind::BitOr)),
token::Shl => Some(Binary(BinOpKind::Shl)),
token::Shr => Some(Binary(BinOpKind::Shr)),
token::PlusEq => Some(AssignOp(BinOpKind::Add)),
token::MinusEq => Some(AssignOp(BinOpKind::Sub)),
token::StarEq => Some(AssignOp(BinOpKind::Mul)),
token::SlashEq => Some(AssignOp(BinOpKind::Div)),
token::PercentEq => Some(AssignOp(BinOpKind::Rem)),
token::CaretEq => Some(AssignOp(BinOpKind::BitXor)),
token::AndEq => Some(AssignOp(BinOpKind::BitAnd)),
token::OrEq => Some(AssignOp(BinOpKind::BitOr)),
token::ShlEq => Some(AssignOp(BinOpKind::Shl)),
token::ShrEq => Some(AssignOp(BinOpKind::Shr)),
token::PlusEq => Some(AssignOp(AssignOpKind::AddAssign)),
token::MinusEq => Some(AssignOp(AssignOpKind::SubAssign)),
token::StarEq => Some(AssignOp(AssignOpKind::MulAssign)),
token::SlashEq => Some(AssignOp(AssignOpKind::DivAssign)),
token::PercentEq => Some(AssignOp(AssignOpKind::RemAssign)),
token::CaretEq => Some(AssignOp(AssignOpKind::BitXorAssign)),
token::AndEq => Some(AssignOp(AssignOpKind::BitAndAssign)),
token::OrEq => Some(AssignOp(AssignOpKind::BitOrAssign)),
token::ShlEq => Some(AssignOp(AssignOpKind::ShlAssign)),
token::ShrEq => Some(AssignOp(AssignOpKind::ShrAssign)),
token::Lt => Some(Binary(BinOpKind::Lt)),
token::Le => Some(Binary(BinOpKind::Le)),
token::Ge => Some(Binary(BinOpKind::Ge)),

View File

@ -274,7 +274,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
}
ExprKind::Assign(el, er, span) => self.lower_expr_assign(el, er, *span, e.span),
ExprKind::AssignOp(op, el, er) => hir::ExprKind::AssignOp(
self.lower_binop(*op),
self.lower_assign_op(*op),
self.lower_expr(el),
self.lower_expr(er),
),
@ -443,6 +443,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
Spanned { node: b.node, span: self.lower_span(b.span) }
}
fn lower_assign_op(&mut self, a: AssignOp) -> AssignOp {
Spanned { node: a.node, span: self.lower_span(a.span) }
}
fn lower_legacy_const_generics(
&mut self,
mut f: Expr,

View File

@ -605,8 +605,7 @@ impl<'a> State<'a> {
fixup.leftmost_subexpression(),
);
self.space();
self.word(op.node.as_str());
self.word_space("=");
self.word_space(op.node.as_str());
self.print_expr_cond_paren(
rhs,
rhs.precedence() < ExprPrecedence::Assign,

View File

@ -10,9 +10,9 @@ use rustc_ast::{
LitKind, TraitObjectSyntax, UintTy, UnsafeBinderCastKind,
};
pub use rustc_ast::{
AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind, BoundConstness, BoundPolarity,
ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto, MetaItemInner, MetaItemLit, Movability,
Mutability, UnOp,
AssignOp, AssignOpKind, AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind,
BoundConstness, BoundPolarity, ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto,
MetaItemInner, MetaItemLit, Movability, Mutability, UnOp,
};
use rustc_attr_data_structures::AttributeKind;
use rustc_data_structures::fingerprint::Fingerprint;
@ -2648,7 +2648,7 @@ pub enum ExprKind<'hir> {
/// An assignment with an operator.
///
/// E.g., `a += 1`.
AssignOp(BinOp, &'hir Expr<'hir>, &'hir Expr<'hir>),
AssignOp(AssignOp, &'hir Expr<'hir>, &'hir Expr<'hir>),
/// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct or tuple field.
Field(&'hir Expr<'hir>, Ident),
/// An indexing operation (`foo[2]`).

View File

@ -1572,8 +1572,7 @@ impl<'a> State<'a> {
hir::ExprKind::AssignOp(op, lhs, rhs) => {
self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign);
self.space();
self.word(op.node.as_str());
self.word_space("=");
self.word_space(op.node.as_str());
self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign);
}
hir::ExprKind::Field(expr, ident) => {

View File

@ -512,7 +512,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_expr_assign(expr, expected, lhs, rhs, span)
}
ExprKind::AssignOp(op, lhs, rhs) => {
self.check_expr_binop_assign(expr, op, lhs, rhs, expected)
self.check_expr_assign_op(expr, op, lhs, rhs, expected)
}
ExprKind::Unary(unop, oprnd) => self.check_expr_unop(unop, oprnd, expected, expr),
ExprKind::AddrOf(kind, mutbl, oprnd) => {

View File

@ -3477,30 +3477,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_ty: Ty<'tcx>,
rhs_expr: &'tcx hir::Expr<'tcx>,
lhs_expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOpKind,
) {
match op {
hir::BinOpKind::Eq => {
if let Some(partial_eq_def_id) = self.infcx.tcx.lang_items().eq_trait()
&& self
.infcx
.type_implements_trait(partial_eq_def_id, [rhs_ty, lhs_ty], self.param_env)
.must_apply_modulo_regions()
{
let sm = self.tcx.sess.source_map();
if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span)
&& let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span)
{
err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`"));
err.multipart_suggestion(
"consider swapping the equality",
vec![(lhs_expr.span, rhs_snippet), (rhs_expr.span, lhs_snippet)],
Applicability::MaybeIncorrect,
);
}
}
if let Some(partial_eq_def_id) = self.infcx.tcx.lang_items().eq_trait()
&& self
.infcx
.type_implements_trait(partial_eq_def_id, [rhs_ty, lhs_ty], self.param_env)
.must_apply_modulo_regions()
{
let sm = self.tcx.sess.source_map();
if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span)
&& let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span)
{
err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`"));
err.multipart_suggestion(
"consider swapping the equality",
vec![(lhs_expr.span, rhs_snippet), (rhs_expr.span, lhs_snippet)],
Applicability::MaybeIncorrect,
);
}
_ => {}
}
}
}

View File

@ -24,22 +24,23 @@ use crate::Expectation;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// Checks a `a <op>= b`
pub(crate) fn check_expr_binop_assign(
pub(crate) fn check_expr_assign_op(
&self,
expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
op: hir::AssignOp,
lhs: &'tcx hir::Expr<'tcx>,
rhs: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected);
self.check_overloaded_binop(expr, lhs, rhs, Op::AssignOp(op), expected);
let category = BinOpCategory::from(op.node);
let ty = if !lhs_ty.is_ty_var()
&& !rhs_ty.is_ty_var()
&& is_builtin_binop(lhs_ty, rhs_ty, op.node)
&& is_builtin_binop(lhs_ty, rhs_ty, category)
{
self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, op.node);
self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, category);
self.tcx.types.unit
} else {
return_ty
@ -51,7 +52,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs, lhs_deref_ty),
Some((rhs, rhs_ty)),
lang_item_for_binop(self.tcx, op.node, IsAssign::Yes),
lang_item_for_binop(self.tcx, Op::AssignOp(op)),
op.span,
expected,
)
@ -63,7 +64,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs, lhs_ty),
Some((rhs, rhs_ty)),
lang_item_for_binop(self.tcx, op.node, IsAssign::Yes),
lang_item_for_binop(self.tcx, Op::AssignOp(op)),
op.span,
expected,
)
@ -118,14 +119,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Otherwise, we always treat operators as if they are
// overloaded. This is the way to be most flexible w/r/t
// types that get inferred.
let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop(
expr,
lhs_expr,
rhs_expr,
op,
IsAssign::No,
expected,
);
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs_expr, rhs_expr, Op::BinOp(op), expected);
// Supply type inference hints if relevant. Probably these
// hints should be enforced during select as part of the
@ -139,16 +134,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// deduce that the result type should be `u32`, even
// though we don't know yet what type 2 has and hence
// can't pin this down to a specific impl.
let category = BinOpCategory::from(op.node);
if !lhs_ty.is_ty_var()
&& !rhs_ty.is_ty_var()
&& is_builtin_binop(lhs_ty, rhs_ty, op.node)
&& is_builtin_binop(lhs_ty, rhs_ty, category)
{
let builtin_return_ty = self.enforce_builtin_binop_types(
lhs_expr.span,
lhs_ty,
rhs_expr.span,
rhs_ty,
op.node,
category,
);
self.demand_eqtype(expr.span, builtin_return_ty, return_ty);
builtin_return_ty
@ -165,16 +161,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_ty: Ty<'tcx>,
rhs_span: Span,
rhs_ty: Ty<'tcx>,
op: hir::BinOpKind,
category: BinOpCategory,
) -> Ty<'tcx> {
debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op));
debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, category));
// Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
// (See https://github.com/rust-lang/rust/issues/57447.)
let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty));
let tcx = self.tcx;
match BinOpCategory::from(op) {
match category {
BinOpCategory::Shortcircuit => {
self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty);
self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty);
@ -205,17 +201,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
lhs_expr: &'tcx hir::Expr<'tcx>,
rhs_expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
is_assign: IsAssign,
op: Op,
expected: Expectation<'tcx>,
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
debug!(
"check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})",
expr.hir_id, op, is_assign
);
debug!("check_overloaded_binop(expr.hir_id={}, op={:?})", expr.hir_id, op);
let lhs_ty = match is_assign {
IsAssign::No => {
let lhs_ty = match op {
Op::BinOp(_) => {
// Find a suitable supertype of the LHS expression's type, by coercing to
// a type variable, to pass as the `Self` to the trait, avoiding invariant
// trait matching creating lifetime constraints that are too strict.
@ -225,7 +217,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let fresh_var = self.next_ty_var(lhs_expr.span);
self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No)
}
IsAssign::Yes => {
Op::AssignOp(_) => {
// rust-lang/rust#52126: We have to use strict
// equivalence on the LHS of an assign-op like `+=`;
// overwritten or mutably-borrowed places cannot be
@ -246,8 +238,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let result = self.lookup_op_method(
(lhs_expr, lhs_ty),
Some((rhs_expr, rhs_ty_var)),
lang_item_for_binop(self.tcx, op.node, is_assign),
op.span,
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
);
@ -257,15 +249,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_ty_var,
Some(lhs_expr),
|err, ty| {
self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr, op.node);
if let Op::BinOp(binop) = op
&& binop.node == hir::BinOpKind::Eq
{
self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr);
}
},
);
let rhs_ty = self.resolve_vars_with_obligations(rhs_ty);
let return_ty = match result {
Ok(method) => {
let by_ref_binop = !op.node.is_by_value();
if is_assign == IsAssign::Yes || by_ref_binop {
let by_ref_binop = !op.is_by_value();
if matches!(op, Op::AssignOp(_)) || by_ref_binop {
if let ty::Ref(_, _, mutbl) = method.sig.inputs()[0].kind() {
let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes);
let autoref = Adjustment {
@ -306,31 +302,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Ty::new_misc_error(self.tcx)
}
Err(errors) => {
let (_, trait_def_id) = lang_item_for_binop(self.tcx, op.node, is_assign);
let (_, trait_def_id) = lang_item_for_binop(self.tcx, op);
let missing_trait = trait_def_id
.map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id)));
let mut path = None;
let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path);
let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path);
let (mut err, output_def_id) = match is_assign {
IsAssign::Yes => {
let (mut err, output_def_id) = match op {
Op::AssignOp(assign_op) => {
let s = assign_op.node.as_str();
let mut err = struct_span_code_err!(
self.dcx(),
expr.span,
E0368,
"binary assignment operation `{}=` cannot be applied to type `{}`",
op.node.as_str(),
"binary assignment operation `{}` cannot be applied to type `{}`",
s,
lhs_ty_str,
);
err.span_label(
lhs_expr.span,
format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty_str),
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
);
self.note_unmet_impls_on_type(&mut err, errors, false);
(err, None)
}
IsAssign::No => {
let message = match op.node {
Op::BinOp(bin_op) => {
let message = match bin_op.node {
hir::BinOpKind::Add => {
format!("cannot add `{rhs_ty_str}` to `{lhs_ty_str}`")
}
@ -366,8 +363,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
_ => format!(
"binary operation `{}` cannot be applied to type `{}`",
op.node.as_str(),
lhs_ty_str,
bin_op.node.as_str(),
lhs_ty_str
),
};
let output_def_id = trait_def_id.and_then(|def_id| {
@ -380,7 +377,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.cloned()
});
let mut err =
struct_span_code_err!(self.dcx(), op.span, E0369, "{message}");
struct_span_code_err!(self.dcx(), bin_op.span, E0369, "{message}");
if !lhs_expr.span.eq(&rhs_expr.span) {
err.span_label(lhs_expr.span, lhs_ty_str.clone());
err.span_label(rhs_expr.span, rhs_ty_str);
@ -413,19 +410,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs_expr, lhs_deref_ty),
Some((rhs_expr, rhs_ty)),
lang_item_for_binop(self.tcx, op.node, is_assign),
op.span,
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.is_ok()
{
let msg = format!(
"`{}{}` can be used on `{}` if you dereference the left-hand side",
op.node.as_str(),
match is_assign {
IsAssign::Yes => "=",
IsAssign::No => "",
},
"`{}` can be used on `{}` if you dereference the left-hand side",
op.as_str(),
self.tcx.short_string(lhs_deref_ty, err.long_ty_path()),
);
err.span_suggestion_verbose(
@ -447,15 +440,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs_expr, lhs_adjusted_ty),
Some((rhs_expr, rhs_adjusted_ty)),
lang_item_for_binop(self.tcx, op.node, is_assign),
op.span,
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.is_ok()
{
let lhs = self.tcx.short_string(lhs_adjusted_ty, err.long_ty_path());
let rhs = self.tcx.short_string(rhs_adjusted_ty, err.long_ty_path());
let op = op.node.as_str();
let op = op.as_str();
err.note(format!("an implementation for `{lhs} {op} {rhs}` exists"));
if let Some(lhs_new_mutbl) = lhs_new_mutbl
@ -505,8 +498,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.lookup_op_method(
(lhs_expr, lhs_ty),
Some((rhs_expr, rhs_ty)),
lang_item_for_binop(self.tcx, op.node, is_assign),
op.span,
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.is_ok()
@ -518,13 +511,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
// `a += b` => `*a += b` if a is a mut ref.
if !op.span.can_be_used_for_suggestions() {
if !op.span().can_be_used_for_suggestions() {
// Suppress suggestions when lhs and rhs are not in the same span as the error
} else if is_assign == IsAssign::Yes
} else if let Op::AssignOp(_) = op
&& let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty)
{
suggest_deref_binop(&mut err, lhs_deref_ty);
} else if is_assign == IsAssign::No
} else if let Op::BinOp(_) = op
&& let ty::Ref(region, lhs_deref_ty, mutbl) = lhs_ty.kind()
{
if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty) {
@ -579,10 +572,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
if let Some(missing_trait) = missing_trait {
if op.node == hir::BinOpKind::Add
&& self.check_str_addition(
lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op,
)
if matches!(
op,
Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
| Op::AssignOp(Spanned { node: hir::AssignOpKind::AddAssign, .. })
) && self
.check_str_addition(lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, op)
{
// This has nothing here because it means we did string
// concatenation (e.g., "Hello " + "World!"). This means
@ -599,8 +594,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs_expr, lhs_ty),
Some((rhs_expr, rhs_ty)),
lang_item_for_binop(self.tcx, op.node, is_assign),
op.span,
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.unwrap_err();
@ -650,9 +645,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Suggest using `add`, `offset` or `offset_from` for pointer - {integer},
// pointer + {integer} or pointer - pointer.
if op.span.can_be_used_for_suggestions() {
match op.node {
hir::BinOpKind::Add if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() => {
if op.span().can_be_used_for_suggestions() {
match op {
Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() =>
{
err.multipart_suggestion(
"consider using `wrapping_add` or `add` for pointer + {integer}",
vec![
@ -665,7 +662,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MaybeIncorrect,
);
}
hir::BinOpKind::Sub => {
Op::BinOp(Spanned { node: hir::BinOpKind::Sub, .. }) => {
if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() {
err.multipart_suggestion(
"consider using `wrapping_sub` or `sub` for \
@ -721,8 +718,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_ty: Ty<'tcx>,
rhs_ty: Ty<'tcx>,
err: &mut Diag<'_>,
is_assign: IsAssign,
op: hir::BinOp,
op: Op,
) -> bool {
let str_concat_note = "string concatenation requires an owned `String` on the left";
let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
@ -741,8 +737,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
r_ty.kind(), ty::Ref(_, inner_ty, _) if *inner_ty.kind() == ty::Str
)) =>
{
if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str`
err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings");
if let Op::BinOp(_) = op { // Do not supply this message if `&str += &str`
err.span_label(
op.span(),
"`+` cannot be used to concatenate two `&str` strings"
);
err.note(str_concat_note);
if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
err.span_suggestion_verbose(
@ -766,11 +765,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if (*l_ty.kind() == ty::Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
{
err.span_label(
op.span,
op.span(),
"`+` cannot be used to concatenate a `&str` with a `String`",
);
match is_assign {
IsAssign::No => {
match op {
Op::BinOp(_) => {
let sugg_msg;
let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
sugg_msg = "remove the borrow on the left and add one on the right";
@ -789,7 +788,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
}
IsAssign::Yes => {
Op::AssignOp(_) => {
err.note(str_concat_note);
}
}
@ -991,37 +990,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
fn lang_item_for_binop(
tcx: TyCtxt<'_>,
op: hir::BinOpKind,
is_assign: IsAssign,
) -> (Symbol, Option<hir::def_id::DefId>) {
fn lang_item_for_binop(tcx: TyCtxt<'_>, op: Op) -> (Symbol, Option<hir::def_id::DefId>) {
let lang = tcx.lang_items();
if is_assign == IsAssign::Yes {
match op {
hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()),
hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()),
hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()),
hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()),
hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()),
hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()),
hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()),
hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()),
hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()),
hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()),
hir::BinOpKind::Lt
| hir::BinOpKind::Le
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt
| hir::BinOpKind::Eq
| hir::BinOpKind::Ne
| hir::BinOpKind::And
| hir::BinOpKind::Or => {
bug!("impossible assignment operation: {}=", op.as_str())
}
}
} else {
match op {
match op {
Op::AssignOp(op) => match op.node {
hir::AssignOpKind::AddAssign => (sym::add_assign, lang.add_assign_trait()),
hir::AssignOpKind::SubAssign => (sym::sub_assign, lang.sub_assign_trait()),
hir::AssignOpKind::MulAssign => (sym::mul_assign, lang.mul_assign_trait()),
hir::AssignOpKind::DivAssign => (sym::div_assign, lang.div_assign_trait()),
hir::AssignOpKind::RemAssign => (sym::rem_assign, lang.rem_assign_trait()),
hir::AssignOpKind::BitXorAssign => (sym::bitxor_assign, lang.bitxor_assign_trait()),
hir::AssignOpKind::BitAndAssign => (sym::bitand_assign, lang.bitand_assign_trait()),
hir::AssignOpKind::BitOrAssign => (sym::bitor_assign, lang.bitor_assign_trait()),
hir::AssignOpKind::ShlAssign => (sym::shl_assign, lang.shl_assign_trait()),
hir::AssignOpKind::ShrAssign => (sym::shr_assign, lang.shr_assign_trait()),
},
Op::BinOp(op) => match op.node {
hir::BinOpKind::Add => (sym::add, lang.add_trait()),
hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
@ -1041,7 +1025,7 @@ fn lang_item_for_binop(
hir::BinOpKind::And | hir::BinOpKind::Or => {
bug!("&& and || are not overloadable")
}
}
},
}
}
@ -1056,6 +1040,7 @@ fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::de
// Binary operator categories. These categories summarize the behavior
// with respect to the builtin operations supported.
#[derive(Clone, Copy)]
enum BinOpCategory {
/// &&, || -- cannot be overridden
Shortcircuit,
@ -1077,38 +1062,58 @@ enum BinOpCategory {
Comparison,
}
impl BinOpCategory {
impl From<hir::BinOpKind> for BinOpCategory {
fn from(op: hir::BinOpKind) -> BinOpCategory {
use hir::BinOpKind::*;
match op {
hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift,
hir::BinOpKind::Add
| hir::BinOpKind::Sub
| hir::BinOpKind::Mul
| hir::BinOpKind::Div
| hir::BinOpKind::Rem => BinOpCategory::Math,
hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => {
BinOpCategory::Bitwise
}
hir::BinOpKind::Eq
| hir::BinOpKind::Ne
| hir::BinOpKind::Lt
| hir::BinOpKind::Le
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt => BinOpCategory::Comparison,
hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit,
Shl | Shr => BinOpCategory::Shift,
Add | Sub | Mul | Div | Rem => BinOpCategory::Math,
BitXor | BitAnd | BitOr => BinOpCategory::Bitwise,
Eq | Ne | Lt | Le | Ge | Gt => BinOpCategory::Comparison,
And | Or => BinOpCategory::Shortcircuit,
}
}
}
/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
impl From<hir::AssignOpKind> for BinOpCategory {
fn from(op: hir::AssignOpKind) -> BinOpCategory {
use hir::AssignOpKind::*;
match op {
ShlAssign | ShrAssign => BinOpCategory::Shift,
AddAssign | SubAssign | MulAssign | DivAssign | RemAssign => BinOpCategory::Math,
BitXorAssign | BitAndAssign | BitOrAssign => BinOpCategory::Bitwise,
}
}
}
/// An assignment op (e.g. `a += b`), or a binary op (e.g. `a + b`).
#[derive(Clone, Copy, Debug, PartialEq)]
enum IsAssign {
No,
Yes,
enum Op {
BinOp(hir::BinOp),
AssignOp(hir::AssignOp),
}
impl Op {
fn span(&self) -> Span {
match self {
Op::BinOp(op) => op.span,
Op::AssignOp(op) => op.span,
}
}
fn as_str(&self) -> &'static str {
match self {
Op::BinOp(op) => op.node.as_str(),
Op::AssignOp(op) => op.node.as_str(),
}
}
fn is_by_value(&self) -> bool {
match self {
Op::BinOp(op) => op.node.is_by_value(),
Op::AssignOp(op) => op.node.is_by_value(),
}
}
}
/// Dereferences a single level of immutable referencing.
@ -1135,27 +1140,24 @@ fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
/// Reason #2 is the killer. I tried for a while to always use
/// overloaded logic and just check the types in constants/codegen after
/// the fact, and it worked fine, except for SIMD types. -nmatsakis
fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOpKind) -> bool {
fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, category: BinOpCategory) -> bool {
// Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
// (See https://github.com/rust-lang/rust/issues/57447.)
let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
match BinOpCategory::from(op) {
match category.into() {
BinOpCategory::Shortcircuit => true,
BinOpCategory::Shift => {
lhs.references_error()
|| rhs.references_error()
|| lhs.is_integral() && rhs.is_integral()
}
BinOpCategory::Math => {
lhs.references_error()
|| rhs.references_error()
|| lhs.is_integral() && rhs.is_integral()
|| lhs.is_floating_point() && rhs.is_floating_point()
}
BinOpCategory::Bitwise => {
lhs.references_error()
|| rhs.references_error()
@ -1163,7 +1165,6 @@ fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOpKind) -> b
|| lhs.is_floating_point() && rhs.is_floating_point()
|| lhs.is_bool() && rhs.is_bool()
}
BinOpCategory::Comparison => {
lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
}

View File

@ -160,7 +160,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
self.typeck_results.node_args_mut().remove(e.hir_id);
}
}
hir::ExprKind::Binary(ref op, lhs, rhs) | hir::ExprKind::AssignOp(ref op, lhs, rhs) => {
hir::ExprKind::Binary(ref op, lhs, rhs) => {
let lhs_ty = self.typeck_results.node_type(lhs.hir_id);
let rhs_ty = self.typeck_results.node_type(rhs.hir_id);
@ -168,25 +168,27 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
self.typeck_results.type_dependent_defs_mut().remove(e.hir_id);
self.typeck_results.node_args_mut().remove(e.hir_id);
match e.kind {
hir::ExprKind::Binary(..) => {
if !op.node.is_by_value() {
let mut adjustments = self.typeck_results.adjustments_mut();
if let Some(a) = adjustments.get_mut(lhs.hir_id) {
a.pop();
}
if let Some(a) = adjustments.get_mut(rhs.hir_id) {
a.pop();
}
}
}
hir::ExprKind::AssignOp(..)
if let Some(a) =
self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) =>
{
if !op.node.is_by_value() {
let mut adjustments = self.typeck_results.adjustments_mut();
if let Some(a) = adjustments.get_mut(lhs.hir_id) {
a.pop();
}
_ => {}
if let Some(a) = adjustments.get_mut(rhs.hir_id) {
a.pop();
}
}
}
}
hir::ExprKind::AssignOp(_, lhs, rhs) => {
let lhs_ty = self.typeck_results.node_type(lhs.hir_id);
let rhs_ty = self.typeck_results.node_type(rhs.hir_id);
if lhs_ty.is_scalar() && rhs_ty.is_scalar() {
self.typeck_results.type_dependent_defs_mut().remove(e.hir_id);
self.typeck_results.node_args_mut().remove(e.hir_id);
if let Some(a) = self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) {
a.pop();
}
}
}

View File

@ -1668,6 +1668,42 @@ pub enum BinOp {
Offset,
}
// Assignment operators, e.g. `+=`. See comments on the corresponding variants
// in `BinOp` for details.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable)]
pub enum AssignOp {
AddAssign,
SubAssign,
MulAssign,
DivAssign,
RemAssign,
BitXorAssign,
BitAndAssign,
BitOrAssign,
ShlAssign,
ShrAssign,
}
// Sometimes `BinOp` and `AssignOp` need the same treatment. The operations
// covered by `AssignOp` are a subset of those covered by `BinOp`, so it makes
// sense to convert `AssignOp` to `BinOp`.
impl From<AssignOp> for BinOp {
fn from(op: AssignOp) -> BinOp {
match op {
AssignOp::AddAssign => BinOp::Add,
AssignOp::SubAssign => BinOp::Sub,
AssignOp::MulAssign => BinOp::Mul,
AssignOp::DivAssign => BinOp::Div,
AssignOp::RemAssign => BinOp::Rem,
AssignOp::BitXorAssign => BinOp::BitXor,
AssignOp::BitAndAssign => BinOp::BitAnd,
AssignOp::BitOrAssign => BinOp::BitOr,
AssignOp::ShlAssign => BinOp::Shl,
AssignOp::ShrAssign => BinOp::Shr,
}
}
}
// Some nodes are used a lot. Make sure they don't unintentionally get bigger.
#[cfg(target_pointer_width = "64")]
mod size_asserts {

View File

@ -27,7 +27,7 @@ use tracing::instrument;
use crate::middle::region;
use crate::mir::interpret::AllocId;
use crate::mir::{self, BinOp, BorrowKind, FakeReadCause, UnOp};
use crate::mir::{self, AssignOp, BinOp, BorrowKind, FakeReadCause, UnOp};
use crate::thir::visit::for_each_immediate_subpat;
use crate::ty::adjustment::PointerCoercion;
use crate::ty::layout::IntegerExt;
@ -403,7 +403,7 @@ pub enum ExprKind<'tcx> {
},
/// A *non-overloaded* operation assignment, e.g. `lhs += rhs`.
AssignOp {
op: BinOp,
op: AssignOp,
lhs: ExprId,
rhs: ExprId,
},

View File

@ -78,8 +78,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// because AssignOp is only legal for Copy types
// (overloaded ops should be desugared into a call).
let result = unpack!(
block =
this.build_binary_op(block, op, expr_span, lhs_ty, Operand::Copy(lhs), rhs)
block = this.build_binary_op(
block,
op.into(),
expr_span,
lhs_ty,
Operand::Copy(lhs),
rhs
)
);
this.cfg.push_assign(block, source_info, lhs, result);

View File

@ -9,7 +9,7 @@ use rustc_middle::hir::place::{
Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind,
};
use rustc_middle::middle::region;
use rustc_middle::mir::{self, BinOp, BorrowKind, UnOp};
use rustc_middle::mir::{self, AssignOp, BinOp, BorrowKind, UnOp};
use rustc_middle::thir::*;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCoercion,
@ -489,7 +489,7 @@ impl<'tcx> ThirBuildCx<'tcx> {
self.overloaded_operator(expr, Box::new([lhs, rhs]))
} else {
ExprKind::AssignOp {
op: bin_op(op.node),
op: assign_op(op.node),
lhs: self.mirror_expr(lhs),
rhs: self.mirror_expr(rhs),
}
@ -1347,3 +1347,18 @@ fn bin_op(op: hir::BinOpKind) -> BinOp {
_ => bug!("no equivalent for ast binop {:?}", op),
}
}
fn assign_op(op: hir::AssignOpKind) -> AssignOp {
match op {
hir::AssignOpKind::AddAssign => AssignOp::AddAssign,
hir::AssignOpKind::SubAssign => AssignOp::SubAssign,
hir::AssignOpKind::MulAssign => AssignOp::MulAssign,
hir::AssignOpKind::DivAssign => AssignOp::DivAssign,
hir::AssignOpKind::RemAssign => AssignOp::RemAssign,
hir::AssignOpKind::BitXorAssign => AssignOp::BitXorAssign,
hir::AssignOpKind::BitAndAssign => AssignOp::BitAndAssign,
hir::AssignOpKind::BitOrAssign => AssignOp::BitOrAssign,
hir::AssignOpKind::ShlAssign => AssignOp::ShlAssign,
hir::AssignOpKind::ShrAssign => AssignOp::ShrAssign,
}
}

View File

@ -14,10 +14,10 @@ use rustc_ast::util::classify;
use rustc_ast::util::parser::{AssocOp, ExprPrecedence, Fixity, prec_let_scrutinee_needs_par};
use rustc_ast::visit::{Visitor, walk_expr};
use rustc_ast::{
self as ast, AnonConst, Arm, AttrStyle, AttrVec, BinOp, BinOpKind, BlockCheckMode, CaptureBy,
ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, FnRetTy, Label, MacCall,
MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind,
YieldKind,
self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind,
BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl,
FnRetTy, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind,
UnOp, UnsafeBinderCastKind, YieldKind,
};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic};
@ -359,7 +359,7 @@ impl<'a> Parser<'a> {
(
Some(
AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge)
| AssocOp::AssignOp(BinOpKind::Shr),
| AssocOp::AssignOp(AssignOpKind::ShrAssign),
),
_,
) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
@ -3914,8 +3914,8 @@ impl<'a> Parser<'a> {
self.dcx().emit_err(errors::LeftArrowOperator { span });
}
fn mk_assign_op(&self, binop: BinOp, lhs: P<Expr>, rhs: P<Expr>) -> ExprKind {
ExprKind::AssignOp(binop, lhs, rhs)
fn mk_assign_op(&self, assign_op: AssignOp, lhs: P<Expr>, rhs: P<Expr>) -> ExprKind {
ExprKind::AssignOp(assign_op, lhs, rhs)
}
fn mk_range(

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher;
use clippy_utils::ty::is_type_lang_item;
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource};
use rustc_hir::{AssignOpKind, Expr, ExprKind, LangItem, MatchSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
@ -77,7 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString {
return;
}
},
ExprKind::AssignOp(op, left, arg) if op.node == BinOpKind::Add && is_string(cx, left) => arg,
ExprKind::AssignOp(op, left, arg) if op.node == AssignOpKind::AddAssign && is_string(cx, left) => arg,
_ => return,
};
if is_format(cx, arg) {

View File

@ -5,7 +5,7 @@ use clippy_utils::source::snippet_with_context;
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_hir::{AssignOpKind, BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{IntTy, Ty, UintTy};
use rustc_session::declare_lint_pass;
@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
&& ex.span.ctxt() == ctxt
&& expr1.span.ctxt() == ctxt
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
&& BinOpKind::Add == op1.node
&& AssignOpKind::AddAssign == op1.node
&& let ExprKind::Lit(lit) = value.kind
&& let LitKind::Int(Pu128(1), LitIntType::Unsuffixed) = lit.node
&& block.expr.is_none()

View File

@ -8,7 +8,7 @@ use clippy_utils::{
use rustc_ast::ast::LitKind;
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath};
use rustc_hir::{AssignOpKind, BinOp, BinOpKind, Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -366,7 +366,7 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp
match peel_blocks_with_stmt(expr).kind {
ExprKind::AssignOp(ref op1, target, value) => {
// Check if literal being subtracted is one
(BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target)
(AssignOpKind::SubAssign == op1.node && is_integer_literal(value, 1)).then_some(target)
},
ExprKind::Assign(target, value, _) => {
if let ExprKind::Binary(ref op1, left1, right1) = value.kind

View File

@ -3,7 +3,9 @@ use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_loc
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{Visitor, walk_expr, walk_local};
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind};
use rustc_hir::{
AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind
};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
@ -58,7 +60,7 @@ impl<'tcx> Visitor<'tcx> for IncrementVisitor<'_, 'tcx> {
match parent.kind {
ExprKind::AssignOp(op, lhs, rhs) => {
if lhs.hir_id == expr.hir_id {
*state = if op.node == BinOpKind::Add
*state = if op.node == AssignOpKind::AddAssign
&& is_integer_const(self.cx, rhs, 1)
&& *state == IncrementVisitorVarState::Initial
&& self.depth == 0

View File

@ -261,10 +261,11 @@ fn check_expr<'tcx>(vis: &mut ReadVisitor<'_, 'tcx>, expr: &'tcx Expr<'_>) -> St
| ExprKind::Assign(..)
| ExprKind::Index(..)
| ExprKind::Repeat(_, _)
| ExprKind::Struct(_, _, _) => {
| ExprKind::Struct(_, _, _)
| ExprKind::AssignOp(_, _, _) => {
walk_expr(vis, expr);
},
ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => {
ExprKind::Binary(op, _, _) => {
if op.node == BinOpKind::And || op.node == BinOpKind::Or {
// x && y and x || y always evaluate x first, so these are
// strictly sequenced.

View File

@ -335,9 +335,12 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects {
return;
}
match &expr.kind {
hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => {
hir::ExprKind::Binary(op, lhs, rhs) => {
self.manage_bin_ops(cx, expr, op.node, lhs, rhs);
},
hir::ExprKind::AssignOp(op, lhs, rhs) => {
self.manage_bin_ops(cx, expr, op.node.into(), lhs, rhs);
},
hir::ExprKind::MethodCall(ps, receiver, args, _) => {
self.manage_method_call(args, cx, expr, ps, receiver);
},

View File

@ -913,9 +913,10 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
);
},
ExprKind::AssignOp(op, lhs, rhs) => {
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
modulo_arithmetic::check(cx, e, op.node, lhs, rhs, false);
let bin_op = op.node.into();
self.arithmetic_context.check_binary(cx, e, bin_op, lhs, rhs);
misrefactored_assign_op::check(cx, e, bin_op, lhs, rhs);
modulo_arithmetic::check(cx, e, bin_op, lhs, rhs, false);
},
ExprKind::Assign(lhs, rhs, _) => {
assign_op_pattern::check(cx, e, lhs, rhs);

View File

@ -5,6 +5,7 @@ use core::ops::ControlFlow;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -56,8 +57,27 @@ declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_
impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind
&& let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop.node)
match expr.kind {
hir::ExprKind::Binary(op, _, _) => {
self.check_expr_inner(cx, expr, op.node, op.span);
}
hir::ExprKind::AssignOp(op, _, _) => {
self.check_expr_inner(cx, expr, op.node.into(), op.span);
}
_ => {}
}
}
}
impl<'tcx> SuspiciousImpl {
fn check_expr_inner(
&mut self,
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
binop: hir::BinOpKind,
span: Span,
) {
if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop)
&& let Some(binop_trait_id) = cx.tcx.lang_items().get(binop_trait_lang)
&& let Some(op_assign_trait_id) = cx.tcx.lang_items().get(op_assign_trait_lang)
@ -82,10 +102,10 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
span_lint(
cx,
lint,
binop.span,
span,
format!(
"suspicious use of `{}` in `{}` impl",
binop.node.as_str(),
binop.as_str(),
cx.tcx.item_name(trait_id)
),
);

View File

@ -10,7 +10,7 @@ use rustc_data_structures::fx::FxIndexSet;
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, LetStmt, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::{AssignOpKind, Block, Expr, ExprKind, LetStmt, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
@ -307,7 +307,7 @@ fn extract_sides_of_xor_assign<'a, 'hir>(
if let StmtKind::Semi(expr) = stmt.kind
&& let ExprKind::AssignOp(
Spanned {
node: BinOpKind::BitXor,
node: AssignOpKind::BitXorAssign,
..
},
lhs,

View File

@ -357,7 +357,7 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
match op {
AssocOp::Binary(op) => format!("{lhs} {} {rhs}", op.as_str()),
AssocOp::Assign => format!("{lhs} = {rhs}"),
AssocOp::AssignOp(op) => format!("{lhs} {}= {rhs}", op.as_str()),
AssocOp::AssignOp(op) => format!("{lhs} {} {rhs}", op.as_str()),
AssocOp::Cast => format!("{lhs} as {rhs}"),
AssocOp::Range(limits) => format!("{lhs}{}{rhs}", limits.as_str()),
}

View File

@ -2058,7 +2058,7 @@ fn rewrite_assignment(
context: &RewriteContext<'_>,
lhs: &ast::Expr,
rhs: &ast::Expr,
op: Option<&ast::BinOp>,
op: Option<&ast::AssignOp>,
shape: Shape,
) -> RewriteResult {
let operator_str = match op {