From a6bd7cc54e193021a3401a627ce573e2a265d11c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 6 Feb 2026 19:48:33 +0100 Subject: [PATCH] make the lint more sophisticated --- Cargo.lock | 1 + compiler/rustc_attr_parsing/Cargo.toml | 1 + .../src/attributes/cfg_select.rs | 107 ++++++++++++++++-- compiler/rustc_lint/messages.ftl | 3 + compiler/rustc_lint/src/early/diagnostics.rs | 10 +- compiler/rustc_lint/src/lints.rs | 7 ++ compiler/rustc_lint_defs/src/lib.rs | 2 +- tests/ui/check-cfg/cfg-select.rs | 4 +- tests/ui/macros/cfg_select.rs | 69 +++++++---- tests/ui/macros/cfg_select.stderr | 44 +++++-- 10 files changed, 194 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ea22911d515..2a4db6d4de4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3539,6 +3539,7 @@ dependencies = [ "rustc_abi", "rustc_ast", "rustc_ast_pretty", + "rustc_data_structures", "rustc_errors", "rustc_feature", "rustc_hir", diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index 411f3f5ccbd1..0a11a2da0dcf 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -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" } diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs index f978d7af67c4..b6cb5b4504ee 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg_select.rs @@ -1,6 +1,7 @@ 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}; @@ -9,11 +10,12 @@ 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 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), @@ -126,19 +128,102 @@ pub fn parse_cfg_select( } } - if let Some((underscore, _, _)) = branches.wildcard - && features.map_or(false, |f| f.enabled(rustc_span::sym::cfg_select)) + if let Some(features) = features + && features.enabled(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 }, + 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, + lint_node_id: NodeId, +) { + // Symbols that have a known value. + let mut known = FxHashMap::::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) + } +} diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 2699889c3730..ff771532ae49 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -999,6 +999,9 @@ 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 diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index b93453d29bea..7681eedc75ed 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -293,9 +293,13 @@ pub fn decorate_builtin_lint( } .decorate_lint(diag); } - BuiltinLintDiag::UnreachableCfg { span, wildcard_span } => { - lints::UnreachableCfgSelectPredicate { span, wildcard_span }.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) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 210bcddcca22..bdc171f11e3b 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3344,6 +3344,13 @@ pub(crate) struct UnknownCrateTypesSuggestion { #[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, diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 76126c96b72a..0c454ec60f4a 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -750,7 +750,7 @@ pub enum BuiltinLintDiag { AttributeLint(AttributeLintKind), UnreachableCfg { span: Span, - wildcard_span: Span, + wildcard_span: Option, }, } diff --git a/tests/ui/check-cfg/cfg-select.rs b/tests/ui/check-cfg/cfg-select.rs index 39703489818d..ffa5e40bff08 100644 --- a/tests/ui/check-cfg/cfg-select.rs +++ b/tests/ui/check-cfg/cfg-select.rs @@ -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 => {} _ => {} } diff --git a/tests/ui/macros/cfg_select.rs b/tests/ui/macros/cfg_select.rs index ecdeb006edf5..09d2c116a86d 100644 --- a/tests/ui/macros/cfg_select.rs +++ b/tests/ui/macros/cfg_select.rs @@ -24,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 + } } } @@ -77,7 +77,7 @@ fn expand_to_pattern(x: Option) -> bool { } cfg_select! { - true => { + false => { fn foo() {} } _ => { @@ -89,7 +89,7 @@ struct S; impl S { cfg_select! { - true => { + false => { fn foo() {} } _ => { @@ -100,7 +100,7 @@ impl S { trait T { cfg_select! { - true => { + false => { fn a(); } _ => { @@ -111,7 +111,7 @@ trait T { impl T for S { cfg_select! { - true => { + false => { fn a() {} } _ => { @@ -122,7 +122,7 @@ impl T for S { extern "C" { cfg_select! { - true => { + false => { fn puts(s: *const i8) -> i32; } _ => { @@ -137,6 +137,25 @@ cfg_select! { //~^ WARN unreachable configuration predicate } +cfg_select! { + true => {} + _ => {} + //~^ WARN unreachable configuration predicate +} + +cfg_select! { + unix => {} + not(unix) => {} + _ => {} + //~^ WARN unreachable configuration predicate +} + +cfg_select! { + unix => {} + unix => {} + //~^ WARN unreachable configuration predicate +} + cfg_select! { //~^ ERROR none of the predicates in this `cfg_select` evaluated to true false => {} diff --git a/tests/ui/macros/cfg_select.stderr b/tests/ui/macros/cfg_select.stderr index 7ef4ec06d93b..be238e0a65e5 100644 --- a/tests/ui/macros/cfg_select.stderr +++ b/tests/ui/macros/cfg_select.stderr @@ -1,5 +1,5 @@ error: none of the predicates in this `cfg_select` evaluated to true - --> $DIR/cfg_select.rs:140:1 + --> $DIR/cfg_select.rs:159:1 | LL | / cfg_select! { LL | | @@ -8,49 +8,49 @@ LL | | } | |_^ error: none of the predicates in this `cfg_select` evaluated to true - --> $DIR/cfg_select.rs:145:1 + --> $DIR/cfg_select.rs:164:1 | LL | cfg_select! {} | ^^^^^^^^^^^^^^ error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>` - --> $DIR/cfg_select.rs:149:5 + --> $DIR/cfg_select.rs:168:5 | LL | => {} | ^^ error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression - --> $DIR/cfg_select.rs:154:5 + --> $DIR/cfg_select.rs:173:5 | LL | () => {} | ^^ expressions are not allowed here error[E0539]: malformed `cfg_select` macro input - --> $DIR/cfg_select.rs:159:5 + --> $DIR/cfg_select.rs:178:5 | LL | "str" => {} | ^^^^^ expected a valid identifier here error[E0539]: malformed `cfg_select` macro input - --> $DIR/cfg_select.rs:164:5 + --> $DIR/cfg_select.rs:183:5 | LL | a::b => {} | ^^^^ expected a valid identifier here error[E0537]: invalid predicate `a` - --> $DIR/cfg_select.rs:169:5 + --> $DIR/cfg_select.rs:188:5 | LL | a() => {} | ^^^ error: expected one of `(`, `::`, `=>`, or `=`, found `+` - --> $DIR/cfg_select.rs:174:7 + --> $DIR/cfg_select.rs:193:7 | LL | a + 1 => {} | ^ expected one of `(`, `::`, `=>`, or `=` error: expected one of `(`, `::`, `=>`, or `=`, found `!` - --> $DIR/cfg_select.rs:180:8 + --> $DIR/cfg_select.rs:199:8 | LL | cfg!() => {} | ^ expected one of `(`, `::`, `=>`, or `=` @@ -69,8 +69,28 @@ note: the lint level is defined here 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 | unix => {} + | ^^^^ this configuration predicate is never reached + warning: unexpected `cfg` condition name: `a` - --> $DIR/cfg_select.rs:174:5 + --> $DIR/cfg_select.rs:193:5 | LL | a + 1 => {} | ^ help: found config with similar value: `target_feature = "a"` @@ -81,7 +101,7 @@ LL | a + 1 => {} = note: `#[warn(unexpected_cfgs)]` on by default warning: unexpected `cfg` condition name: `cfg` - --> $DIR/cfg_select.rs:180:5 + --> $DIR/cfg_select.rs:199:5 | LL | cfg!() => {} | ^^^ @@ -89,7 +109,7 @@ LL | cfg!() => {} = help: to expect this configuration use `--check-cfg=cfg(cfg)` = note: see for more information about checking conditional configuration -error: aborting due to 9 previous errors; 3 warnings emitted +error: aborting due to 9 previous errors; 6 warnings emitted Some errors have detailed explanations: E0537, E0539. For more information about an error, try `rustc --explain E0537`.