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)),