Skip to content

Commit 2df0891

Browse files
committed
Auto merge of #149960 - folkertdev:cfg-select-unreachable-lint, r=JonathanBrouwer,Urgau
add `unreachable_cfg_select_predicates` lint tracking issue: #115585 Split out from #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 #149783 (comment)).
2 parents efc9e1b + 0ebe680 commit 2df0891

File tree

17 files changed

+315
-76
lines changed

17 files changed

+315
-76
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3539,6 +3539,7 @@ dependencies = [
35393539
"rustc_abi",
35403540
"rustc_ast",
35413541
"rustc_ast_pretty",
3542+
"rustc_data_structures",
35423543
"rustc_errors",
35433544
"rustc_feature",
35443545
"rustc_hir",

compiler/rustc_attr_parsing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2024"
88
rustc_abi = { path = "../rustc_abi" }
99
rustc_ast = { path = "../rustc_ast" }
1010
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
11+
rustc_data_structures = { path = "../rustc_data_structures" }
1112
rustc_errors = { path = "../rustc_errors" }
1213
rustc_feature = { path = "../rustc_feature" }
1314
rustc_hir = { path = "../rustc_hir" }

compiler/rustc_attr_parsing/src/attributes/cfg_select.rs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
use rustc_ast::token::Token;
22
use rustc_ast::tokenstream::TokenStream;
33
use rustc_ast::{AttrStyle, NodeId, token};
4+
use rustc_data_structures::fx::FxHashMap;
45
use rustc_feature::{AttributeTemplate, Features};
56
use rustc_hir::attrs::CfgEntry;
67
use rustc_hir::{AttrPath, Target};
78
use rustc_parse::exp;
89
use rustc_parse::parser::{Parser, Recovery};
910
use rustc_session::Session;
10-
use rustc_span::{ErrorGuaranteed, Span, sym};
11+
use rustc_session::lint::BuiltinLintDiag;
12+
use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES;
13+
use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
1114

1215
use crate::parser::MetaItemOrLitParser;
1316
use crate::{AttributeParser, ParsedDescription, ShouldEmit, parse_cfg_entry};
1417

18+
#[derive(Clone)]
1519
pub enum CfgSelectPredicate {
1620
Cfg(CfgEntry),
1721
Wildcard(Token),
1822
}
1923

24+
impl CfgSelectPredicate {
25+
fn span(&self) -> Span {
26+
match self {
27+
CfgSelectPredicate::Cfg(cfg_entry) => cfg_entry.span(),
28+
CfgSelectPredicate::Wildcard(token) => token.span,
29+
}
30+
}
31+
}
32+
2033
#[derive(Default)]
2134
pub struct CfgSelectBranches {
2235
/// All the conditional branches.
@@ -115,5 +128,102 @@ pub fn parse_cfg_select(
115128
}
116129
}
117130

131+
if let Some(features) = features
132+
&& features.enabled(sym::cfg_select)
133+
{
134+
let it = branches
135+
.reachable
136+
.iter()
137+
.map(|(entry, _, _)| CfgSelectPredicate::Cfg(entry.clone()))
138+
.chain(branches.wildcard.as_ref().map(|(t, _, _)| CfgSelectPredicate::Wildcard(*t)))
139+
.chain(
140+
branches.unreachable.iter().map(|(entry, _, _)| CfgSelectPredicate::clone(entry)),
141+
);
142+
143+
lint_unreachable(p, it, lint_node_id);
144+
}
145+
118146
Ok(branches)
119147
}
148+
149+
fn lint_unreachable(
150+
p: &mut Parser<'_>,
151+
predicates: impl Iterator<Item = CfgSelectPredicate>,
152+
lint_node_id: NodeId,
153+
) {
154+
// Symbols that have a known value.
155+
let mut known = FxHashMap::<Symbol, bool>::default();
156+
let mut wildcard_span = None;
157+
let mut it = predicates;
158+
159+
let branch_is_unreachable = |predicate: CfgSelectPredicate, wildcard_span| {
160+
let span = predicate.span();
161+
p.psess.buffer_lint(
162+
UNREACHABLE_CFG_SELECT_PREDICATES,
163+
span,
164+
lint_node_id,
165+
BuiltinLintDiag::UnreachableCfg { span, wildcard_span },
166+
);
167+
};
168+
169+
for predicate in &mut it {
170+
let CfgSelectPredicate::Cfg(ref cfg_entry) = predicate else {
171+
wildcard_span = Some(predicate.span());
172+
break;
173+
};
174+
175+
match cfg_entry {
176+
CfgEntry::Bool(true, _) => {
177+
wildcard_span = Some(predicate.span());
178+
break;
179+
}
180+
CfgEntry::Bool(false, _) => continue,
181+
CfgEntry::NameValue { name, value, .. } => match value {
182+
None => {
183+
// `name` will be false in all subsequent branches.
184+
let current = known.insert(*name, false);
185+
186+
match current {
187+
None => continue,
188+
Some(false) => {
189+
branch_is_unreachable(predicate, None);
190+
break;
191+
}
192+
Some(true) => {
193+
// this branch will be taken, so all subsequent branches are unreachable.
194+
break;
195+
}
196+
}
197+
}
198+
Some(_) => { /* for now we don't bother solving these */ }
199+
},
200+
CfgEntry::Not(inner, _) => match &**inner {
201+
CfgEntry::NameValue { name, value: None, .. } => {
202+
// `name` will be true in all subsequent branches.
203+
let current = known.insert(*name, true);
204+
205+
match current {
206+
None => continue,
207+
Some(true) => {
208+
branch_is_unreachable(predicate, None);
209+
break;
210+
}
211+
Some(false) => {
212+
// this branch will be taken, so all subsequent branches are unreachable.
213+
break;
214+
}
215+
}
216+
}
217+
_ => { /* for now we don't bother solving these */ }
218+
},
219+
CfgEntry::All(_, _) | CfgEntry::Any(_, _) => {
220+
/* for now we don't bother solving these */
221+
}
222+
CfgEntry::Version(..) => { /* don't bother solving these */ }
223+
}
224+
}
225+
226+
for predicate in it {
227+
branch_is_unreachable(predicate, wildcard_span)
228+
}
229+
}

compiler/rustc_builtin_macros/src/cfg_select.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use rustc_ast::tokenstream::TokenStream;
22
use rustc_ast::{Expr, ast};
33
use rustc_attr_parsing as attr;
4-
use rustc_attr_parsing::{
5-
CfgSelectBranches, CfgSelectPredicate, EvalConfigResult, parse_cfg_select,
6-
};
4+
use rustc_attr_parsing::{CfgSelectBranches, EvalConfigResult, parse_cfg_select};
75
use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
86
use rustc_span::{Ident, Span, sym};
97
use smallvec::SmallVec;
108

11-
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable};
9+
use crate::errors::CfgSelectNoMatches;
1210

1311
/// This intermediate structure is used to emit parse errors for the branches that are not chosen.
1412
/// 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>(
7573
ecx.current_expansion.lint_node_id,
7674
) {
7775
Ok(mut branches) => {
78-
if let Some((underscore, _, _)) = branches.wildcard {
79-
// Warn for every unreachable predicate. We store the fully parsed branch for rustfmt.
80-
for (predicate, _, _) in &branches.unreachable {
81-
let span = match predicate {
82-
CfgSelectPredicate::Wildcard(underscore) => underscore.span,
83-
CfgSelectPredicate::Cfg(cfg) => cfg.span(),
84-
};
85-
let err = CfgSelectUnreachable { span, wildcard_span: underscore.span };
86-
ecx.dcx().emit_warn(err);
87-
}
88-
}
89-
9076
if let Some((selected_tts, selected_span)) = branches.pop_first_match(|cfg| {
9177
matches!(attr::eval_config_entry(&ecx.sess, cfg), EvalConfigResult::True)
9278
}) {

compiler/rustc_builtin_macros/src/errors.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,17 +1086,6 @@ pub(crate) struct CfgSelectNoMatches {
10861086
pub span: Span,
10871087
}
10881088

1089-
#[derive(Diagnostic)]
1090-
#[diag("unreachable predicate")]
1091-
pub(crate) struct CfgSelectUnreachable {
1092-
#[primary_span]
1093-
#[label("this predicate is never reached")]
1094-
pub span: Span,
1095-
1096-
#[label("always matches")]
1097-
pub wildcard_span: Span,
1098-
}
1099-
11001089
#[derive(Diagnostic)]
11011090
#[diag("`#[eii_declaration(...)]` is only valid on macros")]
11021091
pub(crate) struct EiiExternTargetExpectedMacro {

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ declare_features! (
392392
(unstable, cfg_sanitize, "1.41.0", Some(39699)),
393393
/// Allows `cfg(sanitizer_cfi_generalize_pointers)` and `cfg(sanitizer_cfi_normalize_integers)`.
394394
(unstable, cfg_sanitizer_cfi, "1.77.0", Some(89653)),
395+
/// Provides a native way to easily manage multiple conditional flags without having to rewrite each clause multiple times.
396+
(unstable, cfg_select, "CURRENT_RUSTC_VERSION", Some(115585)),
395397
/// Allows `cfg(target(abi = "..."))`.
396398
(unstable, cfg_target_compact, "1.63.0", Some(96901)),
397399
/// Allows `cfg(target_has_atomic_load_store = "...")`.

compiler/rustc_lint/messages.ftl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,13 @@ lint_unpredictable_fn_pointer_comparisons = function pointer comparisons do not
998998
999999
lint_unqualified_local_imports = `use` of a local item without leading `self::`, `super::`, or `crate::`
10001000
1001+
lint_unreachable_cfg_select_predicate = unreachable configuration predicate
1002+
.label = this configuration predicate is never reached
1003+
1004+
lint_unreachable_cfg_select_predicate_wildcard = unreachable configuration predicate
1005+
.label = always matches
1006+
.label2 = this configuration predicate is never reached
1007+
10011008
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
10021009
.label = usage of unsafe attribute
10031010
lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`

compiler/rustc_lint/src/early/diagnostics.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,14 @@ pub fn decorate_builtin_lint(
293293
}
294294
.decorate_lint(diag);
295295
}
296+
BuiltinLintDiag::UnreachableCfg { span, wildcard_span } => match wildcard_span {
297+
Some(wildcard_span) => {
298+
lints::UnreachableCfgSelectPredicateWildcard { span, wildcard_span }
299+
.decorate_lint(diag)
300+
}
301+
None => lints::UnreachableCfgSelectPredicate { span }.decorate_lint(diag),
302+
},
303+
296304
BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => {
297305
lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag)
298306
}

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ fn register_builtins(store: &mut LintStore) {
297297
UNUSED_ASSIGNMENTS,
298298
DEAD_CODE,
299299
UNUSED_MUT,
300+
// FIXME: add this lint when it becomes stable,
301+
// see https://github.com/rust-lang/rust/issues/115585.
302+
// UNREACHABLE_CFG_SELECT_PREDICATES,
300303
UNREACHABLE_CODE,
301304
UNREACHABLE_PATTERNS,
302305
UNUSED_MUST_USE,

compiler/rustc_lint/src/lints.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3340,3 +3340,20 @@ pub(crate) struct UnknownCrateTypesSuggestion {
33403340
pub span: Span,
33413341
pub snippet: Symbol,
33423342
}
3343+
3344+
#[derive(LintDiagnostic)]
3345+
#[diag(lint_unreachable_cfg_select_predicate)]
3346+
pub(crate) struct UnreachableCfgSelectPredicate {
3347+
#[label]
3348+
pub span: Span,
3349+
}
3350+
3351+
#[derive(LintDiagnostic)]
3352+
#[diag(lint_unreachable_cfg_select_predicate_wildcard)]
3353+
pub(crate) struct UnreachableCfgSelectPredicateWildcard {
3354+
#[label(lint_label2)]
3355+
pub span: Span,
3356+
3357+
#[label]
3358+
pub wildcard_span: Span,
3359+
}

0 commit comments

Comments
 (0)