| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; | |
| 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::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, | |
| /// as well as method calls applied to closures. | |
| /// | |
| /// ### 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); | |
| }, | |
| _ => (), | |
| } | |
| } else if let ExprKind::MethodCall(method_call) = &expr.kind | |
| && let ExprKind::Closure(closure) = &method_call.receiver.kind | |
| { | |
| span_lint_and_then(cx, PRECEDENCE, expr.span, "precedence might not be obvious", |diag| { | |
| diag.multipart_suggestion( | |
| "consider parenthesizing the closure", | |
| vec![ | |
| (closure.fn_decl_span.shrink_to_lo(), String::from("(")), | |
| (closure.body.span.shrink_to_hi(), String::from(")")), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| }); | |
| } | |
| } | |
| } | |
| 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 | |
| } | |
| } |