rust-lang / rust-lang/rust-clippy
`bad_bit_mask` triggers on expressions containing consts defined as `const _: _ = if cfg!(_) { 0 } else { 1 };`
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
Summary
The lint looks for constants which have compile-time-known value of 0 to warn when they are used as a mask in &. But in this case, it doesn't notice that the value of the constant actually depends on cfg!().
This has the same root cause as https://github.com/rust-lang/rust-clippy/issues/7597, and the fix for that, https://github.com/rust-lang/rust-clippy/pull/8614, introduced a check for the following pattern:
const C: u8 = cfg!(foo);
assert_eq!(C, 0);
by looking whether the initializer (cfg!(foo)) comes from a (direct) expansion of cfg.
My attempt at a solution
One could of course add a check for if cfg!() {} else {} expressions in there, but I think a more future-proof approach would be to call ConstEvalCtxt::eval on the initializer, because that would handle all the ways an expr can be non-const:
diff --git i/clippy_utils/src/consts.rs w/clippy_utils/src/consts.rs
index ecd88daa6..2c07b0d7c 100644
--- i/clippy_utils/src/consts.rs
+++ w/clippy_utils/src/consts.rs
@@ -645,12 +645,8 @@ impl<'tcx> ConstEvalCtxt<'tcx> {
kind: ItemKind::Const(.., body_id),
..
}) = node
- && let Node::Expr(Expr {
- kind: ExprKind::Lit(_),
- span,
- ..
- }) = self.tcx.hir_node(body_id.hir_id)
- && is_direct_expn_of(*span, sym::cfg).is_some()
+ && let Node::Expr(expr) = self.tcx.hir_node(body_id.hir_id)
+ && self.expr(expr).is_none()
{
return None;
}
And that by itself works just fine:
self.expr(expr)goes to theExprKind::Ifcase- that calls
self.ifthenelse(cond, then, otherwise)withcond = cfg!(not(test)) - that calls
self.expr(cond), which goes toExprKind::Litcase - that checks
is_direct_expn_of(cond.span, sym::cfg).is_some(), which comes outtrue - so we go all the way back up and return
Nonefor the const
Unfortunately, it causes an ICE in the following, more complicated case:
const CONDITIONALLY_ZERO: u8 = if cfg!(not(test)) { 0 } else { 1 };
const X: u8 = 0b00001111;
fn foo(x: u8) -> bool {
CONDITIONALLY_ZERO & X == 0
}
and the ICE actually happens when checking X:
- we arrive at the const item node, as before
- we call
self.expr(expr)withexpr = 0b00001111, which goes to theExprKind::Litcase - that calls
is_direct_expn_of(expr.span, sym::cfg).is_some(), which comes outfalse - so we go to the other case, and call
lit_to_mir_constant(&lit.node, self.typeck.expr_ty_opt(expr))) - and
self.typeck.expr_ty_opt(expr)is the part that ends up failing. The reason for that is thatself.typeckbelongs to theOwnerofconst X: u8 = 0b00001111;, which is the surrounding block/module, and so it doesn't acceptexprwhoseOwneris the const itself.
Lint Name
bad_bit_mask
Reproducer
I tried this code:
#![allow(unused)]
#![warn(clippy::bad_bit_mask)]
const CONDITIONALLY_ZERO: u8 = if cfg!(not(test)) { 0 } else { 1 };
fn foo(x: u8) -> bool {
CONDITIONALLY_ZERO & x == 0
}
I saw this happen:
warning: &-masking with zero
--> src/lib.rs:8:5
|
8 | CONDITIONALLY_ZERO & x == 0
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bad_bit_mask
I expected to see this happen:
No lint, because CONDITIONALLY_ZERO is only sometimes 0, depending on the cfg
Version
(playground, 1.91.0-nightly (2025-09-11) and 1.89.0 stable)
Additional Labels
@rustbot label I-false-positive
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in clippy_utils/src/consts.rs, especially ConstEvalCtxt::eval and the initializer handling described in the issue. Reproduce the bad_bit_mask example with CONDITIONALLY_ZERO and the more complicated case involving X, then trace the owner mismatch during constant evaluation. Done means cfg-dependent constants no longer trigger a false-positive lint and the evaluation path does not ICE.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100