add unreachable_cfg_select_predicates lint

This is emitted on branches of a `cfg_select!` that are statically known
to be unreachable.
This commit is contained in:
Folkert de Vries 2025-12-09 19:34:29 +01:00
parent efc9e1b50c
commit 8aea4b1775
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
14 changed files with 146 additions and 47 deletions

View file

@ -7,6 +7,8 @@ use rustc_hir::{AttrPath, Target};
use rustc_parse::exp;
use rustc_parse::parser::{Parser, Recovery};
use rustc_session::Session;
use rustc_session::lint::BuiltinLintDiag;
use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES;
use rustc_span::{ErrorGuaranteed, Span, sym};
use crate::parser::MetaItemOrLitParser;
@ -17,6 +19,15 @@ pub enum CfgSelectPredicate {
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 +126,19 @@ pub fn parse_cfg_select(
}
}
if let Some((underscore, _, _)) = branches.wildcard
&& features.map_or(false, |f| f.enabled(rustc_span::sym::cfg_select))
{
for (predicate, _, _) in &branches.unreachable {
let span = predicate.span();
p.psess.buffer_lint(
UNREACHABLE_CFG_SELECT_PREDICATES,
span,
lint_node_id,
BuiltinLintDiag::UnreachableCfg { span, wildcard_span: underscore.span },
);
}
}
Ok(branches)
}

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

@ -392,6 +392,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,10 @@ 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 = 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,10 @@ pub fn decorate_builtin_lint(
}
.decorate_lint(diag);
}
BuiltinLintDiag::UnreachableCfg { span, wildcard_span } => {
lints::UnreachableCfgSelectPredicate { span, wildcard_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,13 @@ pub(crate) struct UnknownCrateTypesSuggestion {
pub span: Span,
pub snippet: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(lint_unreachable_cfg_select_predicate)]
pub(crate) struct UnreachableCfgSelectPredicate {
#[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: Span,
},
}
#[derive(Debug, HashStable_Generic)]

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! {
@ -133,7 +134,7 @@ extern "C" {
cfg_select! {
_ => {}
true => {}
//~^ WARN unreachable 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:140:1
|
LL | / cfg_select! {
LL | |
@ -16,55 +8,69 @@ LL | | }
| |_^
error: none of the predicates in this `cfg_select` evaluated to true
--> $DIR/cfg_select.rs:144:1
--> $DIR/cfg_select.rs:145: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:149:5
|
LL | => {}
| ^^
error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/cfg_select.rs:153:5
--> $DIR/cfg_select.rs:154:5
|
LL | () => {}
| ^^ expressions are not allowed here
error[E0539]: malformed `cfg_select` macro input
--> $DIR/cfg_select.rs:158:5
--> $DIR/cfg_select.rs:159: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:164:5
|
LL | a::b => {}
| ^^^^ expected a valid identifier here
error[E0537]: invalid predicate `a`
--> $DIR/cfg_select.rs:168:5
--> $DIR/cfg_select.rs:169:5
|
LL | a() => {}
| ^^^
error: expected one of `(`, `::`, `=>`, or `=`, found `+`
--> $DIR/cfg_select.rs:173:7
--> $DIR/cfg_select.rs:174:7
|
LL | a + 1 => {}
| ^ expected one of `(`, `::`, `=>`, or `=`
error: expected one of `(`, `::`, `=>`, or `=`, found `!`
--> $DIR/cfg_select.rs:179:8
--> $DIR/cfg_select.rs:180: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: unexpected `cfg` condition name: `a`
--> $DIR/cfg_select.rs:173:5
--> $DIR/cfg_select.rs:174:5
|
LL | a + 1 => {}
| ^ help: found config with similar value: `target_feature = "a"`
@ -75,7 +81,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:180:5
|
LL | cfg!() => {}
| ^^^