Skip to content

Commit

Permalink
New lint: precedence_bits, with recent additions to precedence (#…
Browse files Browse the repository at this point in the history
…14115)

Commit 2550530 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.

Fix #14097

changelog: [`precedence_bits`]: new lint
  • Loading branch information
llogiq authored Jan 30, 2025
2 parents f51e18d + 8db9ecf commit 398a5c2
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5954,6 +5954,7 @@ Released 2018-09-13
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
[`precedence_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence_bits
[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl
[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::permissions_set_readonly_false::PERMISSIONS_SET_READONLY_FALSE_INFO,
crate::pointers_in_nomem_asm_block::POINTERS_IN_NOMEM_ASM_BLOCK_INFO,
crate::precedence::PRECEDENCE_INFO,
crate::precedence::PRECEDENCE_BITS_INFO,
crate::ptr::CMP_NULL_INFO,
crate::ptr::INVALID_NULL_PTR_USAGE_INFO,
crate::ptr::MUT_FROM_REF_INFO,
Expand Down
68 changes: 47 additions & 21 deletions clippy_lints/src/precedence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,47 @@ 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};
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. Currently it catches the following:
/// * mixed usage of arithmetic and bit shifting/combining operators without
/// parentheses
/// * mixed usage of bitmasking and bit shifting operators without parentheses
/// 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
/// * `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2
/// `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_lint_pass!(Precedence => [PRECEDENCE]);
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) {
Expand All @@ -38,10 +52,10 @@ impl EarlyLintPass for Precedence {
}

if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind {
let span_sugg = |expr: &Expr, sugg, appl| {
let span_sugg = |lint: &'static Lint, expr: &Expr, sugg, appl| {
span_lint_and_sugg(
cx,
PRECEDENCE,
lint,
expr.span,
"operator precedence might not be obvious",
"consider parenthesizing your expression",
Expand All @@ -57,37 +71,41 @@ impl EarlyLintPass for Precedence {
match (op, get_bin_opt(left), get_bin_opt(right)) {
(
BitAnd | BitOr | BitXor,
Some(Shl | Shr | Add | Div | Mul | Rem | Sub),
Some(Shl | Shr | Add | Div | Mul | Rem | Sub),
Some(left_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)),
Some(right_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)),
)
| (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), Some(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(expr, sugg, applicability);
span_sugg(lint_for(&[op, left_op, right_op]), expr, sugg, applicability);
},
(BitAnd | BitOr | BitXor, Some(Shl | Shr | Add | Div | Mul | Rem | Sub), _)
| (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), _) => {
(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(expr, sugg, applicability);
span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability);
},
(BitAnd | BitOr | BitXor, _, Some(Shl | Shr | Add | Div | Mul | Rem | Sub))
| (Shl | Shr, _, Some(Add | Div | Mul | Rem | Sub)) => {
(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(expr, sugg, applicability);
span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability);
},
_ => (),
}
Expand All @@ -106,3 +124,11 @@ fn get_bin_opt(expr: &Expr) -> Option<BinOpKind> {
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
}
}
8 changes: 4 additions & 4 deletions tests/ui/precedence.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ fn main() {
1 ^ (1 - 1);
3 | (2 - 1);
3 & (5 - 2);
0x0F00 & (0x00F0 << 4);
0x0F00 & (0xF000 >> 4);
(0x0F00 << 1) ^ 3;
(0x0F00 << 1) | 2;
0x0F00 & 0x00F0 << 4;
0x0F00 & 0xF000 >> 4;
0x0F00 << 1 ^ 3;
0x0F00 << 1 | 2;

let b = 3;
trip!(b * 8);
Expand Down
26 changes: 1 addition & 25 deletions tests/ui/precedence.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,5 @@ error: operator precedence might not be obvious
LL | 3 & 5 - 2;
| ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)`

error: operator precedence might not be obvious
--> tests/ui/precedence.rs:23:5
|
LL | 0x0F00 & 0x00F0 << 4;
| ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)`

error: operator precedence might not be obvious
--> tests/ui/precedence.rs:24:5
|
LL | 0x0F00 & 0xF000 >> 4;
| ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)`

error: operator precedence might not be obvious
--> tests/ui/precedence.rs:25:5
|
LL | 0x0F00 << 1 ^ 3;
| ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3`

error: operator precedence might not be obvious
--> tests/ui/precedence.rs:26:5
|
LL | 0x0F00 << 1 | 2;
| ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) | 2`

error: aborting due to 11 previous errors
error: aborting due to 7 previous errors

35 changes: 35 additions & 0 deletions tests/ui/precedence_bits.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![warn(clippy::precedence_bits)]
#![allow(
unused_must_use,
clippy::no_effect,
clippy::unnecessary_operation,
clippy::precedence
)]
#![allow(clippy::identity_op)]
#![allow(clippy::eq_op)]

macro_rules! trip {
($a:expr) => {
match $a & 0b1111_1111u8 {
0 => println!("a is zero ({})", $a),
_ => println!("a is {}", $a),
}
};
}

fn main() {
1 << 2 + 3;
1 + 2 << 3;
4 >> 1 + 1;
1 + 3 >> 2;
1 ^ 1 - 1;
3 | 2 - 1;
3 & 5 - 2;
0x0F00 & (0x00F0 << 4);
0x0F00 & (0xF000 >> 4);
(0x0F00 << 1) ^ 3;
(0x0F00 << 1) | 2;

let b = 3;
trip!(b * 8);
}
35 changes: 35 additions & 0 deletions tests/ui/precedence_bits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#![warn(clippy::precedence_bits)]
#![allow(
unused_must_use,
clippy::no_effect,
clippy::unnecessary_operation,
clippy::precedence
)]
#![allow(clippy::identity_op)]
#![allow(clippy::eq_op)]

macro_rules! trip {
($a:expr) => {
match $a & 0b1111_1111u8 {
0 => println!("a is zero ({})", $a),
_ => println!("a is {}", $a),
}
};
}

fn main() {
1 << 2 + 3;
1 + 2 << 3;
4 >> 1 + 1;
1 + 3 >> 2;
1 ^ 1 - 1;
3 | 2 - 1;
3 & 5 - 2;
0x0F00 & 0x00F0 << 4;
0x0F00 & 0xF000 >> 4;
0x0F00 << 1 ^ 3;
0x0F00 << 1 | 2;

let b = 3;
trip!(b * 8);
}
29 changes: 29 additions & 0 deletions tests/ui/precedence_bits.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
error: operator precedence might not be obvious
--> tests/ui/precedence_bits.rs:28:5
|
LL | 0x0F00 & 0x00F0 << 4;
| ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)`
|
= note: `-D clippy::precedence-bits` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::precedence_bits)]`

error: operator precedence might not be obvious
--> tests/ui/precedence_bits.rs:29:5
|
LL | 0x0F00 & 0xF000 >> 4;
| ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)`

error: operator precedence might not be obvious
--> tests/ui/precedence_bits.rs:30:5
|
LL | 0x0F00 << 1 ^ 3;
| ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3`

error: operator precedence might not be obvious
--> tests/ui/precedence_bits.rs:31:5
|
LL | 0x0F00 << 1 | 2;
| ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) | 2`

error: aborting due to 4 previous errors

0 comments on commit 398a5c2

Please sign in to comment.