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 { 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 } }