blob: ec6835db897ed9c251020024eccba226f3a8658a [file] [log] [blame]
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
}
}