rust/clippy_lints/src/precedence.rs
Samuel Tardieu 8db9ecfd74 New lint: precedence_bits, with recent additions to precedence
Commit 2550530266 has extended the
`precedence` lint to include bitmasking and shift operations. The lint
is warn by default, and this generates many hits, especially in embedded
or system code, where it is very idiomatic to use expressions such as
`1 << 3 | 1 << 5` without parentheses.

This commit splits the recent addition into a new lint, which is put
into the "restriction" category, while the original one stays in
"complexity", because mixing bitmasking and arithmetic operations is
less typical.
2025-01-30 17:07:50 +01:00

134 lines
5.1 KiB
Rust

use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
use rustc_ast::ast::{BinOpKind, Expr, ExprKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, Lint};
use rustc_session::declare_lint_pass;
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
/// Checks for operations where precedence may be unclear and suggests to add parentheses.
/// It catches a mixed usage of arithmetic and bit shifting/combining operators without parentheses
///
/// ### Why is this bad?
/// Not everyone knows the precedence of those operators by
/// heart, so expressions like these may trip others trying to reason about the
/// code.
///
/// ### Example
/// `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7
#[clippy::version = "pre 1.29.0"]
pub PRECEDENCE,
complexity,
"operations where precedence may be unclear"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for bit shifting operations combined with bit masking/combining operators
/// and suggest using parentheses.
///
/// ### Why restrict this?
/// Not everyone knows the precedence of those operators by
/// heart, so expressions like these may trip others trying to reason about the
/// code.
///
/// ### Example
/// `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2
#[clippy::version = "1.86.0"]
pub PRECEDENCE_BITS,
restriction,
"operations mixing bit shifting with bit combining/masking"
}
declare_lint_pass!(Precedence => [PRECEDENCE, PRECEDENCE_BITS]);
impl EarlyLintPass for Precedence {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind {
let span_sugg = |lint: &'static Lint, expr: &Expr, sugg, appl| {
span_lint_and_sugg(
cx,
lint,
expr.span,
"operator precedence might not be obvious",
"consider parenthesizing your expression",
sugg,
appl,
);
};
if !is_bit_op(op) {
return;
}
let mut applicability = Applicability::MachineApplicable;
match (op, get_bin_opt(left), get_bin_opt(right)) {
(
BitAnd | BitOr | BitXor,
Some(left_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)),
Some(right_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)),
)
| (
Shl | Shr,
Some(left_op @ (Add | Div | Mul | Rem | Sub)),
Some(right_op @ (Add | Div | Mul | Rem | Sub)),
) => {
let sugg = format!(
"({}) {} ({})",
snippet_with_applicability(cx, left.span, "..", &mut applicability),
op.as_str(),
snippet_with_applicability(cx, right.span, "..", &mut applicability)
);
span_sugg(lint_for(&[op, left_op, right_op]), expr, sugg, applicability);
},
(BitAnd | BitOr | BitXor, Some(side_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), _)
| (Shl | Shr, Some(side_op @ (Add | Div | Mul | Rem | Sub)), _) => {
let sugg = format!(
"({}) {} {}",
snippet_with_applicability(cx, left.span, "..", &mut applicability),
op.as_str(),
snippet_with_applicability(cx, right.span, "..", &mut applicability)
);
span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability);
},
(BitAnd | BitOr | BitXor, _, Some(side_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)))
| (Shl | Shr, _, Some(side_op @ (Add | Div | Mul | Rem | Sub))) => {
let sugg = format!(
"{} {} ({})",
snippet_with_applicability(cx, left.span, "..", &mut applicability),
op.as_str(),
snippet_with_applicability(cx, right.span, "..", &mut applicability)
);
span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability);
},
_ => (),
}
}
}
}
fn get_bin_opt(expr: &Expr) -> Option<BinOpKind> {
match expr.kind {
ExprKind::Binary(Spanned { node: op, .. }, _, _) => Some(op),
_ => None,
}
}
#[must_use]
fn is_bit_op(op: BinOpKind) -> bool {
matches!(op, BitXor | BitAnd | BitOr | Shl | Shr)
}
fn lint_for(ops: &[BinOpKind]) -> &'static Lint {
if ops.iter().all(|op| is_bit_op(*op)) {
PRECEDENCE_BITS
} else {
PRECEDENCE
}
}