From 8aea4b17751d19abbef91c72aa59c25671711acb Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 9 Dec 2025 19:34:29 +0100 Subject: [PATCH] add `unreachable_cfg_select_predicates` lint This is emitted on branches of a `cfg_select!` that are statically known to be unreachable. --- .../src/attributes/cfg_select.rs | 25 +++++++++++ .../rustc_builtin_macros/src/cfg_select.rs | 18 +------- compiler/rustc_builtin_macros/src/errors.rs | 11 ----- compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_lint/messages.ftl | 4 ++ compiler/rustc_lint/src/early/diagnostics.rs | 4 ++ compiler/rustc_lint/src/lib.rs | 3 ++ compiler/rustc_lint/src/lints.rs | 10 +++++ compiler/rustc_lint_defs/src/builtin.rs | 29 ++++++++++++ compiler/rustc_lint_defs/src/lib.rs | 4 ++ .../feature-gates/feature-gate-cfg-select.rs | 11 +++++ .../feature-gate-cfg-select.stderr | 25 +++++++++++ tests/ui/macros/cfg_select.rs | 3 +- tests/ui/macros/cfg_select.stderr | 44 +++++++++++-------- 14 files changed, 146 insertions(+), 47 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-cfg-select.rs create mode 100644 tests/ui/feature-gates/feature-gate-cfg-select.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index 4005ad2cba11..f978d7af67c4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -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) } diff --git a/compiler/rustc_builtin_macros/src/cfg_select.rs b/compiler/rustc_builtin_macros/src/cfg_select.rs index f11190b28105..35098722a910 100644 --- a/compiler/rustc_builtin_macros/src/cfg_select.rs +++ b/compiler/rustc_builtin_macros/src/cfg_select.rs @@ -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) }) { diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index d3f7e1c5d8e3..43bade3c43ac 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -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 { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index c99af9658cde..6bdf9f04405f 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -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 = "...")`. diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 2f5b7ed26952..2699889c3730 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -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(...)` diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index c0ab0d1f0040..b93453d29bea 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -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) } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 80b32645a895..ea31a6373cbc 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -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, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 1a87cc013e79..210bcddcca22 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -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, +} diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index f4e6e93356c7..488e3a70b669 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -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. diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index b3e5b93cf2fc..76126c96b72a 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -748,6 +748,10 @@ pub enum BuiltinLintDiag { }, UnusedVisibility(Span), AttributeLint(AttributeLintKind), + UnreachableCfg { + span: Span, + wildcard_span: Span, + }, } #[derive(Debug, HashStable_Generic)] diff --git a/tests/ui/feature-gates/feature-gate-cfg-select.rs b/tests/ui/feature-gates/feature-gate-cfg-select.rs new file mode 100644 index 000000000000..0963ed001508 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-select.rs @@ -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() {} diff --git a/tests/ui/feature-gates/feature-gate-cfg-select.stderr b/tests/ui/feature-gates/feature-gate-cfg-select.stderr new file mode 100644 index 000000000000..0fdce4975164 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-cfg-select.stderr @@ -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 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 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`. diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index 2369158ba82a..ecdeb006edf5 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -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! { diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index ffd8540425ab..7ef4ec06d93b 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -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!() => {} | ^^^