Rollup merge of #149960 - folkertdev:cfg-select-unreachable-lint, r=JonathanBrouwer

add `unreachable_cfg_select_predicates` lint

tracking issue: https://github.com/rust-lang/rust/issues/115585

Split out from https://github.com/rust-lang/rust/pull/149783. This lint is emitted on branches of a `cfg_select!` that are statically known to be unreachable. The lint is only emitted when the feature is enabled, so this change specifically does not need an FCP, and the lint will be stabilized alongside the feature (see https://github.com/rust-lang/rust/pull/149783#issuecomment-3648000286).
This commit is contained in:
Jonathan Brouwer 2026-02-07 16:04:40 +01:00 committed by GitHub
commit 972a53167c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 323 additions and 76 deletions

View file

@ -3539,6 +3539,7 @@ dependencies = [
"rustc_abi",
"rustc_ast",
"rustc_ast_pretty",
"rustc_data_structures",
"rustc_errors",
"rustc_feature",
"rustc_hir",

View file

@ -8,6 +8,7 @@ edition = "2024"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_feature = { path = "../rustc_feature" }
rustc_hir = { path = "../rustc_hir" }

View file

@ -1,22 +1,35 @@
use rustc_ast::token::Token;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{AttrStyle, NodeId, token};
use rustc_data_structures::fx::FxHashMap;
use rustc_feature::{AttributeTemplate, Features};
use rustc_hir::attrs::CfgEntry;
use rustc_hir::{AttrPath, Target};
use rustc_parse::exp;
use rustc_parse::parser::{Parser, Recovery};
use rustc_session::Session;
use rustc_span::{ErrorGuaranteed, Span, sym};
use rustc_session::lint::BuiltinLintDiag;
use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES;
use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
use crate::parser::MetaItemOrLitParser;
use crate::{AttributeParser, ParsedDescription, ShouldEmit, parse_cfg_entry};
#[derive(Clone)]
pub enum CfgSelectPredicate {
Cfg(CfgEntry),
Wildcard(Token),
}
impl CfgSelectPredicate {
fn span(&self) -> Span {
match self {
CfgSelectPredicate::Cfg(cfg_entry) => cfg_entry.span(),
CfgSelectPredicate::Wildcard(token) => token.span,
}
}
}
#[derive(Default)]
pub struct CfgSelectBranches {
/// All the conditional branches.
@ -115,5 +128,102 @@ pub fn parse_cfg_select(
}
}
if let Some(features) = features
&& features.enabled(sym::cfg_select)
{
let it = branches
.reachable
.iter()
.map(|(entry, _, _)| CfgSelectPredicate::Cfg(entry.clone()))
.chain(branches.wildcard.as_ref().map(|(t, _, _)| CfgSelectPredicate::Wildcard(*t)))
.chain(
branches.unreachable.iter().map(|(entry, _, _)| CfgSelectPredicate::clone(entry)),
);
lint_unreachable(p, it, lint_node_id);
}
Ok(branches)
}
fn lint_unreachable(
p: &mut Parser<'_>,
predicates: impl Iterator<Item = CfgSelectPredicate>,
lint_node_id: NodeId,
) {
// Symbols that have a known value.
let mut known = FxHashMap::<Symbol, bool>::default();
let mut wildcard_span = None;
let mut it = predicates;
let branch_is_unreachable = |predicate: CfgSelectPredicate, wildcard_span| {
let span = predicate.span();
p.psess.buffer_lint(
UNREACHABLE_CFG_SELECT_PREDICATES,
span,
lint_node_id,
BuiltinLintDiag::UnreachableCfg { span, wildcard_span },
);
};
for predicate in &mut it {
let CfgSelectPredicate::Cfg(ref cfg_entry) = predicate else {
wildcard_span = Some(predicate.span());
break;
};
match cfg_entry {
CfgEntry::Bool(true, _) => {
wildcard_span = Some(predicate.span());
break;
}
CfgEntry::Bool(false, _) => continue,
CfgEntry::NameValue { name, value, .. } => match value {
None => {
// `name` will be false in all subsequent branches.
let current = known.insert(*name, false);
match current {
None => continue,
Some(false) => {
branch_is_unreachable(predicate, None);
break;
}
Some(true) => {
// this branch will be taken, so all subsequent branches are unreachable.
break;
}
}
}
Some(_) => { /* for now we don't bother solving these */ }
},
CfgEntry::Not(inner, _) => match &**inner {
CfgEntry::NameValue { name, value: None, .. } => {
// `name` will be true in all subsequent branches.
let current = known.insert(*name, true);
match current {
None => continue,
Some(true) => {
branch_is_unreachable(predicate, None);
break;
}
Some(false) => {
// this branch will be taken, so all subsequent branches are unreachable.
break;
}
}
}
_ => { /* for now we don't bother solving these */ }
},
CfgEntry::All(_, _) | CfgEntry::Any(_, _) => {
/* for now we don't bother solving these */
}
CfgEntry::Version(..) => { /* don't bother solving these */ }
}
}
for predicate in it {
branch_is_unreachable(predicate, wildcard_span)
}
}

View file

@ -1,14 +1,12 @@
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{Expr, ast};
use rustc_attr_parsing as attr;
use rustc_attr_parsing::{
CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select,
};
use rustc_attr_parsing::{CfgSelectBranches, EvalConfigResult, parse_cfg_select};
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
use rustc_span::{Ident, Span, sym};
use smallvec::SmallVec;
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
use crate::errors::CfgSelectNoMatches;
/// This intermediate structure is used to emit parse errors for the branches that are not chosen.
/// The `MacResult` instance below parses all branches, emitting any errors it encounters, but only
@ -75,18 +73,6 @@ pub(super) fn expand_cfg_select<'cx>(
ecx.current_expansion.lint_node_id,
) {
Ok(mut branches) => {
if let Some((underscore, _, _)) = branches.wildcard {
// Warn for every unreachable predicate. We store the fully parsed branch for rustfmt.
for (predicate, _, _) in &branches.unreachable {
let span = match predicate {
CfgSelectPredicate::Wildcard(underscore) => underscore.span,
CfgSelectPredicate::Cfg(cfg) => cfg.span(),
};
let err = CfgSelectUnreachable { span, wildcard_span: underscore.span };
ecx.dcx().emit_warn(err);
}
}
if let Some((selected_tts, selected_span)) = branches.pop_first_match(|cfg| {
matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True)
}) {

View file

@ -1086,17 +1086,6 @@ pub(crate) struct CfgSelectNoMatches {
pub span: Span,
}
#[derive(Diagnostic)]
#[diag("unreachable predicate")]
pub(crate) struct CfgSelectUnreachable {
#[primary_span]
#[label("this predicate is never reached")]
pub span: Span,
#[label("always matches")]
pub wildcard_span: Span,
}
#[derive(Diagnostic)]
#[diag("`#[eii_declaration(...)]` is only valid on macros")]
pub(crate) struct EiiExternTargetExpectedMacro {

View file

@ -394,6 +394,8 @@ declare_features! (
(unstable, cfg_sanitize, "1.41.0", Some(39699)),
/// Allows `cfg(sanitizer_cfi_generalize_pointers)` and `cfg(sanitizer_cfi_normalize_integers)`.
(unstable, cfg_sanitizer_cfi, "1.77.0", Some(89653)),
/// Provides a native way to easily manage multiple conditional flags without having to rewrite each clause multiple times.
(unstable, cfg_select, "CURRENT_RUSTC_VERSION", Some(115585)),
/// Allows `cfg(target(abi = "..."))`.
(unstable, cfg_target_compact, "1.63.0", Some(96901)),
/// Allows `cfg(target_has_atomic_load_store = "...")`.

View file

@ -998,6 +998,13 @@ lint_unpredictable_fn_pointer_comparisons = function pointer comparisons do not
lint_unqualified_local_imports = `use` of a local item without leading `self::`, `super::`, or `crate::`
lint_unreachable_cfg_select_predicate = unreachable configuration predicate
.label = this configuration predicate is never reached
lint_unreachable_cfg_select_predicate_wildcard = unreachable configuration predicate
.label = always matches
.label2 = this configuration predicate is never reached
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
.label = usage of unsafe attribute
lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`

View file

@ -293,6 +293,14 @@ pub fn decorate_builtin_lint(
}
.decorate_lint(diag);
}
BuiltinLintDiag::UnreachableCfg { span, wildcard_span } => match wildcard_span {
Some(wildcard_span) => {
lints::UnreachableCfgSelectPredicateWildcard { span, wildcard_span }
.decorate_lint(diag)
}
None => lints::UnreachableCfgSelectPredicate { span }.decorate_lint(diag),
},
BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => {
lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag)
}

View file

@ -297,6 +297,9 @@ fn register_builtins(store: &mut LintStore) {
UNUSED_ASSIGNMENTS,
DEAD_CODE,
UNUSED_MUT,
// FIXME: add this lint when it becomes stable,
// see https://github.com/rust-lang/rust/issues/115585.
// UNREACHABLE_CFG_SELECT_PREDICATES,
UNREACHABLE_CODE,
UNREACHABLE_PATTERNS,
UNUSED_MUST_USE,

View file

@ -3340,3 +3340,20 @@ pub(crate) struct UnknownCrateTypesSuggestion {
pub span: Span,
pub snippet: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(lint_unreachable_cfg_select_predicate)]
pub(crate) struct UnreachableCfgSelectPredicate {
#[label]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(lint_unreachable_cfg_select_predicate_wildcard)]
pub(crate) struct UnreachableCfgSelectPredicateWildcard {
#[label(lint_label2)]
pub span: Span,
#[label]
pub wildcard_span: Span,
}

View file

@ -123,6 +123,7 @@ declare_lint_pass! {
UNKNOWN_LINTS,
UNNAMEABLE_TEST_ITEMS,
UNNAMEABLE_TYPES,
UNREACHABLE_CFG_SELECT_PREDICATES,
UNREACHABLE_CODE,
UNREACHABLE_PATTERNS,
UNSAFE_ATTR_OUTSIDE_UNSAFE,
@ -855,6 +856,34 @@ declare_lint! {
"detects unreachable patterns"
}
declare_lint! {
/// The `unreachable_cfg_select_predicates` lint detects unreachable configuration
/// predicates in the `cfg_select!` macro.
///
/// ### Example
///
/// ```rust
/// #![feature(cfg_select)]
/// cfg_select! {
/// _ => (),
/// windows => (),
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// This usually indicates a mistake in how the predicates are specified or
/// ordered. In this example, the `_` predicate will always match, so the
/// `windows` is impossible to reach. Remember, arms match in order, you
/// probably wanted to put the `windows` case above the `_` case.
pub UNREACHABLE_CFG_SELECT_PREDICATES,
Warn,
"detects unreachable configuration predicates in the cfg_select macro",
@feature_gate = cfg_select;
}
declare_lint! {
/// The `overlapping_range_endpoints` lint detects `match` arms that have [range patterns] that
/// overlap on their endpoints.

View file

@ -748,6 +748,10 @@ pub enum BuiltinLintDiag {
},
UnusedVisibility(Span),
AttributeLint(AttributeLintKind),
UnreachableCfg {
span: Span,
wildcard_span: Option<Span>,
},
}
#[derive(Debug, HashStable_Generic)]

View file

@ -4,7 +4,7 @@
#![crate_type = "lib"]
cfg_select! {
true => {}
false => {}
invalid_cfg1 => {}
//~^ WARN unexpected `cfg` condition name
_ => {}
@ -13,6 +13,6 @@ cfg_select! {
cfg_select! {
invalid_cfg2 => {}
//~^ WARN unexpected `cfg` condition name
true => {}
false => {}
_ => {}
}

View file

@ -0,0 +1,11 @@
#![warn(unreachable_cfg_select_predicates)]
//~^ WARN unknown lint: `unreachable_cfg_select_predicates`
cfg_select! {
//~^ ERROR use of unstable library feature `cfg_select`
_ => {}
// With the feature enabled, this branch would trip the unreachable_cfg_select_predicate lint.
true => {}
}
fn main() {}

View file

@ -0,0 +1,25 @@
error[E0658]: use of unstable library feature `cfg_select`
--> $DIR/feature-gate-cfg-select.rs:4:1
|
LL | cfg_select! {
| ^^^^^^^^^^
|
= note: see issue #115585 <https://github.com/rust-lang/rust/issues/115585> for more information
= help: add `#![feature(cfg_select)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
warning: unknown lint: `unreachable_cfg_select_predicates`
--> $DIR/feature-gate-cfg-select.rs:1:9
|
LL | #![warn(unreachable_cfg_select_predicates)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the `unreachable_cfg_select_predicates` lint is unstable
= note: see issue #115585 <https://github.com/rust-lang/rust/issues/115585> for more information
= help: add `#![feature(cfg_select)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= note: `#[warn(unknown_lints)]` on by default
error: aborting due to 1 previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0658`.

View file

@ -1,5 +1,6 @@
#![feature(cfg_select)]
#![crate_type = "lib"]
#![warn(unreachable_cfg_select_predicates)] // Unused warnings are disabled by default in UI tests.
fn print() {
println!(cfg_select! {
@ -23,40 +24,40 @@ fn arm_rhs_expr_1() -> i32 {
fn arm_rhs_expr_2() -> i32 {
cfg_select! {
true => 1,
false => 2
false => 2,
true => 1
}
}
fn arm_rhs_expr_3() -> i32 {
cfg_select! {
true => 1,
false => 2,
true => { 42 }
false => -1 as i32,
true => 2 + 2,
false => "",
true => if true { 42 } else { 84 }
false => if true { 42 } else { 84 },
true => return 42,
false => loop {}
true => (1, 2),
false => (1, 2,),
true => todo!(),
false => println!("hello"),
any(true) => 1,
any(false) => 2,
any(true) => { 42 }
any(false) => -1 as i32,
any(true) => 2 + 2,
any(false) => "",
any(true) => if true { 42 } else { 84 }
any(false) => if true { 42 } else { 84 },
any(true) => return 42,
any(false) => loop {}
any(true) => (1, 2),
any(false) => (1, 2,),
any(true) => todo!(),
any(false) => println!("hello"),
}
}
fn expand_to_statements() -> i32 {
cfg_select! {
true => {
let a = 1;
a + 1
}
false => {
let b = 2;
b + 1
}
true => {
let a = 1;
a + 1
}
}
}
@ -76,7 +77,7 @@ fn expand_to_pattern(x: Option<i32>) -> bool {
}
cfg_select! {
true => {
false => {
fn foo() {}
}
_ => {
@ -88,7 +89,7 @@ struct S;
impl S {
cfg_select! {
true => {
false => {
fn foo() {}
}
_ => {
@ -99,7 +100,7 @@ impl S {
trait T {
cfg_select! {
true => {
false => {
fn a();
}
_ => {
@ -110,7 +111,7 @@ trait T {
impl T for S {
cfg_select! {
true => {
false => {
fn a() {}
}
_ => {
@ -121,7 +122,7 @@ impl T for S {
extern "C" {
cfg_select! {
true => {
false => {
fn puts(s: *const i8) -> i32;
}
_ => {
@ -133,7 +134,28 @@ extern "C" {
cfg_select! {
_ => {}
true => {}
//~^ WARN unreachable predicate
//~^ WARN unreachable configuration predicate
}
cfg_select! {
true => {}
_ => {}
//~^ WARN unreachable configuration predicate
}
cfg_select! {
unix => {}
not(unix) => {}
_ => {}
//~^ WARN unreachable configuration predicate
}
cfg_select! {
test => {}
test => {}
//~^ WARN unreachable configuration predicate
_ => {}
//~^ WARN unreachable configuration predicate
}
cfg_select! {

View file

@ -1,13 +1,5 @@
warning: unreachable predicate
--> $DIR/cfg_select.rs:135:5
|
LL | _ => {}
| - always matches
LL | true => {}
| ^^^^ this predicate is never reached
error: none of the predicates in this `cfg_select` evaluated to true
--> $DIR/cfg_select.rs:139:1
--> $DIR/cfg_select.rs:161:1
|
LL | / cfg_select! {
LL | |
@ -16,55 +8,95 @@ LL | | }
| |_^
error: none of the predicates in this `cfg_select` evaluated to true
--> $DIR/cfg_select.rs:144:1
--> $DIR/cfg_select.rs:166:1
|
LL | cfg_select! {}
| ^^^^^^^^^^^^^^
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>`
--> $DIR/cfg_select.rs:148:5
--> $DIR/cfg_select.rs:170:5
|
LL | => {}
| ^^
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/cfg_select.rs:153:5
--> $DIR/cfg_select.rs:175:5
|
LL | () => {}
| ^^ expressions are not allowed here
error[E0539]: malformed `cfg_select` macro input
--> $DIR/cfg_select.rs:158:5
--> $DIR/cfg_select.rs:180:5
|
LL | "str" => {}
| ^^^^^ expected a valid identifier here
error[E0539]: malformed `cfg_select` macro input
--> $DIR/cfg_select.rs:163:5
--> $DIR/cfg_select.rs:185:5
|
LL | a::b => {}
| ^^^^ expected a valid identifier here
error[E0537]: invalid predicate `a`
--> $DIR/cfg_select.rs:168:5
--> $DIR/cfg_select.rs:190:5
|
LL | a() => {}
| ^^^
error: expected one of `(`, `::`, `=>`, or `=`, found `+`
--> $DIR/cfg_select.rs:173:7
--> $DIR/cfg_select.rs:195:7
|
LL | a + 1 => {}
| ^ expected one of `(`, `::`, `=>`, or `=`
error: expected one of `(`, `::`, `=>`, or `=`, found `!`
--> $DIR/cfg_select.rs:179:8
--> $DIR/cfg_select.rs:201:8
|
LL | cfg!() => {}
| ^ expected one of `(`, `::`, `=>`, or `=`
warning: unreachable configuration predicate
--> $DIR/cfg_select.rs:136:5
|
LL | _ => {}
| - always matches
LL | true => {}
| ^^^^ this configuration predicate is never reached
|
note: the lint level is defined here
--> $DIR/cfg_select.rs:3:9
|
LL | #![warn(unreachable_cfg_select_predicates)] // Unused warnings are disabled by default in UI tests.
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: unreachable configuration predicate
--> $DIR/cfg_select.rs:142:5
|
LL | true => {}
| ---- always matches
LL | _ => {}
| ^ this configuration predicate is never reached
warning: unreachable configuration predicate
--> $DIR/cfg_select.rs:149:5
|
LL | _ => {}
| ^ this configuration predicate is never reached
warning: unreachable configuration predicate
--> $DIR/cfg_select.rs:155:5
|
LL | test => {}
| ^^^^ this configuration predicate is never reached
warning: unreachable configuration predicate
--> $DIR/cfg_select.rs:157:5
|
LL | _ => {}
| ^ this configuration predicate is never reached
warning: unexpected `cfg` condition name: `a`
--> $DIR/cfg_select.rs:173:5
--> $DIR/cfg_select.rs:195:5
|
LL | a + 1 => {}
| ^ help: found config with similar value: `target_feature = "a"`
@ -75,7 +107,7 @@ LL | a + 1 => {}
= note: `#[warn(unexpected_cfgs)]` on by default
warning: unexpected `cfg` condition name: `cfg`
--> $DIR/cfg_select.rs:179:5
--> $DIR/cfg_select.rs:201:5
|
LL | cfg!() => {}
| ^^^
@ -83,7 +115,7 @@ LL | cfg!() => {}
= help: to expect this configuration use `--check-cfg=cfg(cfg)`
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more information about checking conditional configuration
error: aborting due to 9 previous errors; 3 warnings emitted
error: aborting due to 9 previous errors; 7 warnings emitted
Some errors have detailed explanations: E0537, E0539.
For more information about an error, try `rustc --explain E0537`.