Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/cfg_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use rustc_hir::attrs::CfgEntry;
use rustc_parse::exp;
use rustc_parse::parser::Parser;
use rustc_session::Session;
use rustc_session::lint::BuiltinLintDiag;
use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES;
use rustc_span::{ErrorGuaranteed, Ident, Span};

use crate::parser::MetaItemOrLitParser;
Expand All @@ -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.
Expand Down Expand Up @@ -86,5 +97,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 },
);
}
}
Comment on lines +100 to +112
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to warn, unless there is a wildcard, why so? Does it not warn in case of

cfg_select! {
    unix => true,
    not(unix) => false,
    windows => false,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct, it's hard to do better than that. we'd need to actually have some sort of logic solver to do so in general, and even then I think it might misfire if there is some sort of feature flag implication that the checker does not know about.

So the current imlementation is basic but reliable.


Ok(branches)
}
4 changes: 0 additions & 4 deletions compiler/rustc_builtin_macros/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ builtin_macros_cfg_accessible_unspecified_path = `cfg_accessible` path is not sp

builtin_macros_cfg_select_no_matches = none of the predicates in this `cfg_select` evaluated to true

builtin_macros_cfg_select_unreachable = unreachable predicate
.label = always matches
.label2 = this predicate is never reached

builtin_macros_coerce_pointee_requires_maybe_sized = `derive(CoercePointee)` requires `{$name}` to be marked `?Sized`

builtin_macros_coerce_pointee_requires_one_field = `CoercePointee` can only be derived on `struct`s with at least one field
Expand Down
18 changes: 2 additions & 16 deletions compiler/rustc_builtin_macros/src/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use rustc_ast::tokenstream::TokenStream;
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, MacroExpanderResult};
use rustc_span::{Ident, Span, sym};

use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
use crate::errors::CfgSelectNoMatches;

/// Selects the first arm whose predicate evaluates to true.
fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> {
Expand All @@ -32,18 +30,6 @@ pub(super) fn expand_cfg_select<'cx>(
ecx.current_expansion.lint_node_id,
) {
Ok(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((tts, arm_span)) = select_arm(ecx, branches) {
return ExpandResult::from_tts(
ecx,
Expand Down
11 changes: 0 additions & 11 deletions compiler/rustc_builtin_macros/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,17 +1000,6 @@ pub(crate) struct CfgSelectNoMatches {
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_cfg_select_unreachable)]
pub(crate) struct CfgSelectUnreachable {
#[primary_span]
#[label(builtin_macros_label2)]
pub span: Span,

#[label]
pub wildcard_span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_eii_extern_target_expected_macro)]
pub(crate) struct EiiExternTargetExpectedMacro {
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,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(...)`
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/early/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ fn register_builtins(store: &mut LintStore) {
UNUSED_ASSIGNMENTS,
DEAD_CODE,
UNUSED_MUT,
UNREACHABLE_CFG_SELECT_PREDICATES,
UNREACHABLE_CODE,
UNREACHABLE_PATTERNS,
UNUSED_MUST_USE,
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3298,3 +3298,13 @@ pub(crate) struct DocTestUnknown {
#[derive(LintDiagnostic)]
#[diag(lint_doc_test_literal)]
pub(crate) struct DocTestLiteral;

#[derive(LintDiagnostic)]
#[diag(lint_unreachable_cfg_select_predicate)]
pub(crate) struct UnreachableCfgSelectPredicate {
#[label(lint_label2)]
pub span: Span,

#[label]
pub wildcard_span: Span,
}
28 changes: 28 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ declare_lint_pass! {
UNKNOWN_LINTS,
UNNAMEABLE_TEST_ITEMS,
UNNAMEABLE_TYPES,
UNREACHABLE_CFG_SELECT_PREDICATES,
UNREACHABLE_CODE,
UNREACHABLE_PATTERNS,
UNSAFE_ATTR_OUTSIDE_UNSAFE,
Expand Down Expand Up @@ -852,6 +853,33 @@ 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"
}

declare_lint! {
/// The `overlapping_range_endpoints` lint detects `match` arms that have [range patterns] that
/// overlap on their endpoints.
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,10 @@ pub enum BuiltinLintDiag {
},
UnusedVisibility(Span),
AttributeLint(AttributeLintKind),
UnreachableCfg {
span: Span,
wildcard_span: Span,
},
}

#[derive(Debug, HashStable_Generic)]
Expand Down
13 changes: 13 additions & 0 deletions tests/ui/feature-gates/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#![warn(unreachable_cfg_select_predicates)] // Unused warnings are disabled by default in UI tests.

// `#[feature(cfg_select)]` is a libs feature (so, not a lang feature), but it lints on unreachable
// branches, and that lint should only be emitted when the feature is enabled.
Comment on lines +3 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: why should it only lint when the feature is enabled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because then no separate FCP is required to merge this lint. At least that is my interpretation of #149783 (comment).

Copy link
Contributor

@traviscross traviscross Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably what I had in mind is to go ahead and make it into (or otherwise add) a lang feature gate and then to feature gate the lint itself in the declare_lint!.


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() {}
13 changes: 13 additions & 0 deletions tests/ui/feature-gates/cfg_select.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error[E0658]: use of unstable library feature `cfg_select`
--> $DIR/cfg_select.rs:6: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

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0658`.
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,21 @@ note: the lint level is defined here
LL | #![forbid(forbidden_lint_groups)]
| ^^^^^^^^^^^^^^^^^^^^^

Future breakage diagnostic:
error: warn(unused) incompatible with previous forbid
--> $DIR/issue-70819-dont-override-forbid-in-same-scope.rs:22:13
|
LL | #![forbid(unused)]
| ------ `forbid` level set here
LL | #![deny(unused)]
LL | #![warn(unused)]
| ^^^^^^ overruled by previous forbid
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #81670 <https://github.com/rust-lang/rust/issues/81670>
note: the lint level is defined here
--> $DIR/issue-70819-dont-override-forbid-in-same-scope.rs:17:11
|
LL | #![forbid(forbidden_lint_groups)]
| ^^^^^^^^^^^^^^^^^^^^^

Comment on lines +434 to +451
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened, how is this change related?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test has this comment

// If you turn off deduplicate diagnostics (which rustc turns on by default but
// compiletest turns off when it runs ui tests), then the errors are
// (unfortunately) repeated here because the checking is done as we read in the
// errors, and currently that happens two or three different times, depending on
// compiler flags.
//
// I decided avoiding the redundant output was not worth the time in engineering
// effort for bug like this, which 1. end users are unlikely to run into in the
// first place, and 2. they won't see the redundant output anyway.

//@ compile-flags: -Z deduplicate-diagnostics=yes

I believe that the logic here added another time that the error is emitted. That's unfortunate but as the comment mentions it's kind of not worth the engineering effort to fix because it is unlikely that end users ever see this repeated error.

18 changes: 18 additions & 0 deletions tests/ui/lint/outer-forbid.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,21 @@ note: the lint level is defined here
LL | #![forbid(forbidden_lint_groups)]
| ^^^^^^^^^^^^^^^^^^^^^

Future breakage diagnostic:
error: allow(unused) incompatible with previous forbid
--> $DIR/outer-forbid.rs:25:9
|
LL | #![forbid(unused, non_snake_case)]
| ------ `forbid` level set here
...
LL | #[allow(unused)]
| ^^^^^^ overruled by previous forbid
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #81670 <https://github.com/rust-lang/rust/issues/81670>
note: the lint level is defined here
--> $DIR/outer-forbid.rs:18:11
|
LL | #![forbid(forbidden_lint_groups)]
| ^^^^^^^^^^^^^^^^^^^^^

3 changes: 2 additions & 1 deletion tests/ui/macros/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -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! {
Expand Down Expand Up @@ -50,7 +51,7 @@ fn arm_rhs_expr_3() -> i32 {
cfg_select! {
_ => {}
true => {}
//~^ WARN unreachable predicate
//~^ WARN unreachable configuration predicate
}

cfg_select! {
Expand Down
44 changes: 25 additions & 19 deletions tests/ui/macros/cfg_select.stderr
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
warning: unreachable predicate
--> $DIR/cfg_select.rs:52: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:56:1
--> $DIR/cfg_select.rs:57:1
|
LL | / cfg_select! {
LL | |
Expand All @@ -16,57 +8,71 @@ LL | | }
| |_^

error: none of the predicates in this `cfg_select` evaluated to true
--> $DIR/cfg_select.rs:61:1
--> $DIR/cfg_select.rs:62:1
|
LL | cfg_select! {}
| ^^^^^^^^^^^^^^

error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found `=>`
--> $DIR/cfg_select.rs:65:5
--> $DIR/cfg_select.rs:66:5
|
LL | => {}
| ^^

error: expected a literal (`1u8`, `1.0f32`, `"string"`, etc.) here, found expression
--> $DIR/cfg_select.rs:70:5
--> $DIR/cfg_select.rs:71:5
|
LL | () => {}
| ^^ expressions are not allowed here

error[E0539]: malformed `cfg_select` macro input
--> $DIR/cfg_select.rs:75:5
--> $DIR/cfg_select.rs:76:5
|
LL | "str" => {}
| ^^^^^ expected a valid identifier here
|

error[E0539]: malformed `cfg_select` macro input
--> $DIR/cfg_select.rs:80:5
--> $DIR/cfg_select.rs:81:5
|
LL | a::b => {}
| ^^^^ expected a valid identifier here
|

error[E0537]: invalid predicate `a`
--> $DIR/cfg_select.rs:85:5
--> $DIR/cfg_select.rs:86:5
|
LL | a() => {}
| ^^^

error: expected one of `(`, `::`, `=>`, or `=`, found `+`
--> $DIR/cfg_select.rs:90:7
--> $DIR/cfg_select.rs:91:7
|
LL | a + 1 => {}
| ^ expected one of `(`, `::`, `=>`, or `=`

error: expected one of `(`, `::`, `=>`, or `=`, found `!`
--> $DIR/cfg_select.rs:96:8
--> $DIR/cfg_select.rs:97:8
|
LL | cfg!() => {}
| ^ expected one of `(`, `::`, `=>`, or `=`

warning: unreachable configuration predicate
--> $DIR/cfg_select.rs:53: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:90:5
--> $DIR/cfg_select.rs:91:5
|
LL | a + 1 => {}
| ^ help: found config with similar value: `target_feature = "a"`
Expand All @@ -77,7 +83,7 @@ LL | a + 1 => {}
= note: `#[warn(unexpected_cfgs)]` on by default

warning: unexpected `cfg` condition name: `cfg`
--> $DIR/cfg_select.rs:96:5
--> $DIR/cfg_select.rs:97:5
|
LL | cfg!() => {}
| ^^^
Expand Down
Loading