Skip to content

Commit ded0a9e

Browse files
authored
Rollup merge of #111068 - Urgau:check-cfg-improvements, r=petrochenkov
Improve check-cfg implementation This PR makes multiple improvements into the implementation of check-cfg, it is a prerequisite to a follow-up PR that will introduce a simpler and more explicit syntax. The 2 main area of improvements are: 1. Internal representation of expected values: - now uses `FxHashSet<Option<Symbol>>` instead of `FxHashSet<Symbol>`, it made the no value expected case only possible when no values where in the `HashSet` which is now represented as `None` (same as cfg represent-it). - a enum with `Some` and `Any` makes it now clear if some values are expected or not, necessary for `feature` and `target_feature`. 2. Diagnostics: Improve the diagnostics in multiple case and fix case where a missing value could have had a new name suggestion instead of the value diagnostic; and some drive by improvements I highly recommend reviewing commit by commit. r? `@petrochenkov`
2 parents 65702bf + 5364784 commit ded0a9e

19 files changed

+420
-236
lines changed

compiler/rustc_attr/src/builtin.rs

+20-19
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_ast::{Attribute, LitKind, MetaItem, MetaItemKind, MetaItemLit, NestedM
55
use rustc_ast_pretty::pprust;
66
use rustc_feature::{find_gated_cfg, is_builtin_attr_name, Features, GatedCfg};
77
use rustc_macros::HashStable_Generic;
8+
use rustc_session::config::ExpectedValues;
89
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
910
use rustc_session::lint::BuiltinLintDiagnostics;
1011
use rustc_session::parse::{feature_err, ParseSess};
@@ -581,32 +582,32 @@ pub fn cfg_matches(
581582
) -> bool {
582583
eval_condition(cfg, sess, features, &mut |cfg| {
583584
try_gate_cfg(cfg.name, cfg.span, sess, features);
584-
if let Some(names_valid) = &sess.check_config.names_valid {
585-
if !names_valid.contains(&cfg.name) {
585+
match sess.check_config.expecteds.get(&cfg.name) {
586+
Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
586587
sess.buffer_lint_with_diagnostic(
587588
UNEXPECTED_CFGS,
588589
cfg.span,
589590
lint_node_id,
590-
"unexpected `cfg` condition name",
591-
BuiltinLintDiagnostics::UnexpectedCfg((cfg.name, cfg.name_span), None),
591+
"unexpected `cfg` condition value",
592+
BuiltinLintDiagnostics::UnexpectedCfgValue(
593+
(cfg.name, cfg.name_span),
594+
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
595+
),
592596
);
593597
}
594-
}
595-
if let Some(value) = cfg.value {
596-
if let Some(values) = &sess.check_config.values_valid.get(&cfg.name) {
597-
if !values.contains(&value) {
598-
sess.buffer_lint_with_diagnostic(
599-
UNEXPECTED_CFGS,
600-
cfg.span,
601-
lint_node_id,
602-
"unexpected `cfg` condition value",
603-
BuiltinLintDiagnostics::UnexpectedCfg(
604-
(cfg.name, cfg.name_span),
605-
cfg.value_span.map(|vs| (value, vs)),
606-
),
607-
);
608-
}
598+
None if sess.check_config.exhaustive_names => {
599+
sess.buffer_lint_with_diagnostic(
600+
UNEXPECTED_CFGS,
601+
cfg.span,
602+
lint_node_id,
603+
"unexpected `cfg` condition name",
604+
BuiltinLintDiagnostics::UnexpectedCfgName(
605+
(cfg.name, cfg.name_span),
606+
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
607+
),
608+
);
609609
}
610+
_ => { /* not unexpected */ }
610611
}
611612
sess.config.contains(&(cfg.name, cfg.value))
612613
})

compiler/rustc_interface/src/interface.rs

+48-28
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ use rustc_data_structures::OnDrop;
99
use rustc_errors::registry::Registry;
1010
use rustc_errors::{ErrorGuaranteed, Handler};
1111
use rustc_lint::LintStore;
12-
use rustc_middle::ty;
12+
use rustc_middle::{bug, ty};
1313
use rustc_parse::maybe_new_parser_from_source_str;
1414
use rustc_query_impl::QueryCtxt;
1515
use rustc_query_system::query::print_query_stack;
16-
use rustc_session::config::{self, CheckCfg, ErrorOutputType, Input, OutputFilenames};
16+
use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames};
17+
use rustc_session::config::{CheckCfg, ExpectedValues};
1718
use rustc_session::lint;
1819
use rustc_session::parse::{CrateConfig, ParseSess};
1920
use rustc_session::Session;
@@ -121,9 +122,9 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String
121122
/// Converts strings provided as `--check-cfg [specs]` into a `CheckCfg`.
122123
pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
123124
rustc_span::create_default_session_if_not_set_then(move |_| {
124-
let mut cfg = CheckCfg::default();
125+
let mut check_cfg = CheckCfg::default();
125126

126-
'specs: for s in specs {
127+
for s in specs {
127128
let sess = ParseSess::with_silent_emitter(Some(format!(
128129
"this error occurred on the command line: `--check-cfg={s}`"
129130
)));
@@ -137,76 +138,95 @@ pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
137138
concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"),
138139
s
139140
),
140-
);
141+
)
141142
};
142143
}
143144

145+
let expected_error = || {
146+
error!(
147+
"expected `names(name1, name2, ... nameN)` or \
148+
`values(name, \"value1\", \"value2\", ... \"valueN\")`"
149+
)
150+
};
151+
144152
match maybe_new_parser_from_source_str(&sess, filename, s.to_string()) {
145153
Ok(mut parser) => match parser.parse_meta_item() {
146154
Ok(meta_item) if parser.token == token::Eof => {
147155
if let Some(args) = meta_item.meta_item_list() {
148156
if meta_item.has_name(sym::names) {
149-
let names_valid =
150-
cfg.names_valid.get_or_insert_with(|| FxHashSet::default());
157+
check_cfg.exhaustive_names = true;
151158
for arg in args {
152159
if arg.is_word() && arg.ident().is_some() {
153160
let ident = arg.ident().expect("multi-segment cfg key");
154-
names_valid.insert(ident.name.to_string());
161+
check_cfg
162+
.expecteds
163+
.entry(ident.name.to_string())
164+
.or_insert(ExpectedValues::Any);
155165
} else {
156166
error!("`names()` arguments must be simple identifiers");
157167
}
158168
}
159-
continue 'specs;
160169
} else if meta_item.has_name(sym::values) {
161170
if let Some((name, values)) = args.split_first() {
162171
if name.is_word() && name.ident().is_some() {
163172
let ident = name.ident().expect("multi-segment cfg key");
164-
let ident_values = cfg
165-
.values_valid
173+
let expected_values = check_cfg
174+
.expecteds
166175
.entry(ident.name.to_string())
167-
.or_insert_with(|| FxHashSet::default());
176+
.or_insert_with(|| {
177+
ExpectedValues::Some(FxHashSet::default())
178+
});
179+
180+
let ExpectedValues::Some(expected_values) = expected_values else {
181+
bug!("shoudn't be possible")
182+
};
168183

169184
for val in values {
170185
if let Some(LitKind::Str(s, _)) =
171186
val.lit().map(|lit| &lit.kind)
172187
{
173-
ident_values.insert(s.to_string());
188+
expected_values.insert(Some(s.to_string()));
174189
} else {
175190
error!(
176191
"`values()` arguments must be string literals"
177192
);
178193
}
179194
}
180195

181-
continue 'specs;
196+
if values.is_empty() {
197+
expected_values.insert(None);
198+
}
182199
} else {
183200
error!(
184201
"`values()` first argument must be a simple identifier"
185202
);
186203
}
187204
} else if args.is_empty() {
188-
cfg.well_known_values = true;
189-
continue 'specs;
205+
check_cfg.exhaustive_values = true;
206+
} else {
207+
expected_error();
190208
}
209+
} else {
210+
expected_error();
191211
}
212+
} else {
213+
expected_error();
192214
}
193215
}
194-
Ok(..) => {}
195-
Err(err) => err.cancel(),
216+
Ok(..) => expected_error(),
217+
Err(err) => {
218+
err.cancel();
219+
expected_error();
220+
}
196221
},
197-
Err(errs) => drop(errs),
222+
Err(errs) => {
223+
drop(errs);
224+
expected_error();
225+
}
198226
}
199-
200-
error!(
201-
"expected `names(name1, name2, ... nameN)` or \
202-
`values(name, \"value1\", \"value2\", ... \"valueN\")`"
203-
);
204227
}
205228

206-
if let Some(names_valid) = &mut cfg.names_valid {
207-
names_valid.extend(cfg.values_valid.keys().cloned());
208-
}
209-
cfg
229+
check_cfg
210230
})
211231
}
212232

compiler/rustc_lint/src/builtin.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ use rustc_middle::ty::layout::{LayoutError, LayoutOf};
6363
use rustc_middle::ty::print::with_no_trimmed_paths;
6464
use rustc_middle::ty::subst::GenericArgKind;
6565
use rustc_middle::ty::{self, Instance, Ty, TyCtxt, VariantDef};
66+
use rustc_session::config::ExpectedValues;
6667
use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
6768
use rustc_span::edition::Edition;
6869
use rustc_span::source_map::Spanned;
@@ -3306,16 +3307,15 @@ impl EarlyLintPass for UnexpectedCfgs {
33063307
let cfg = &cx.sess().parse_sess.config;
33073308
let check_cfg = &cx.sess().parse_sess.check_config;
33083309
for &(name, value) in cfg {
3309-
if let Some(names_valid) = &check_cfg.names_valid && !names_valid.contains(&name){
3310-
cx.emit_lint(UNEXPECTED_CFGS, BuiltinUnexpectedCliConfigName {
3311-
name,
3312-
});
3313-
}
3314-
if let Some(value) = value && let Some(values) = check_cfg.values_valid.get(&name) && !values.contains(&value) {
3315-
cx.emit_lint(
3316-
UNEXPECTED_CFGS,
3317-
BuiltinUnexpectedCliConfigValue { name, value },
3318-
);
3310+
match check_cfg.expecteds.get(&name) {
3311+
Some(ExpectedValues::Some(values)) if !values.contains(&value) => {
3312+
let value = value.unwrap_or(kw::Empty);
3313+
cx.emit_lint(UNEXPECTED_CFGS, BuiltinUnexpectedCliConfigValue { name, value });
3314+
}
3315+
None if check_cfg.exhaustive_names => {
3316+
cx.emit_lint(UNEXPECTED_CFGS, BuiltinUnexpectedCliConfigName { name });
3317+
}
3318+
_ => { /* expected */ }
33193319
}
33203320
}
33213321
}

compiler/rustc_lint/src/context.rs

+54-16
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use rustc_middle::middle::stability;
3636
use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
3737
use rustc_middle::ty::print::with_no_trimmed_paths;
3838
use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt};
39+
use rustc_session::config::ExpectedValues;
3940
use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
4041
use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
4142
use rustc_session::Session;
@@ -768,22 +769,52 @@ pub trait LintContext: Sized {
768769
db.help(help);
769770
db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information");
770771
},
771-
BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), None) => {
772-
let Some(names_valid) = &sess.parse_sess.check_config.names_valid else {
773-
bug!("it shouldn't be possible to have a diagnostic on a name if name checking is not enabled");
774-
};
775-
let possibilities: Vec<Symbol> = names_valid.iter().map(|s| *s).collect();
772+
BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => {
773+
let possibilities: Vec<Symbol> = sess.parse_sess.check_config.expecteds.keys().map(|s| *s).collect();
776774

777775
// Suggest the most probable if we found one
778776
if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
779-
db.span_suggestion(name_span, "did you mean", best_match, Applicability::MaybeIncorrect);
777+
if let Some(ExpectedValues::Some(best_match_values)) =
778+
sess.parse_sess.check_config.expecteds.get(&best_match) {
779+
let mut possibilities = best_match_values.iter()
780+
.flatten()
781+
.map(Symbol::as_str)
782+
.collect::<Vec<_>>();
783+
possibilities.sort();
784+
785+
if let Some((value, value_span)) = value {
786+
if best_match_values.contains(&Some(value)) {
787+
db.span_suggestion(name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect);
788+
} else if best_match_values.contains(&None) {
789+
db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and no value", best_match, Applicability::MaybeIncorrect);
790+
} else if let Some(first_value) = possibilities.first() {
791+
db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", format!("{best_match} = \"{first_value}\""), Applicability::MaybeIncorrect);
792+
} else {
793+
db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", best_match, Applicability::MaybeIncorrect);
794+
};
795+
} else {
796+
db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
797+
}
798+
799+
if !possibilities.is_empty() {
800+
let possibilities = possibilities.join("`, `");
801+
db.help(format!("expected values for `{best_match}` are: `{possibilities}`"));
802+
}
803+
} else {
804+
db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
805+
}
780806
}
781807
},
782-
BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), Some((value, value_span))) => {
783-
let Some(values) = &sess.parse_sess.check_config.values_valid.get(&name) else {
808+
BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => {
809+
let Some(ExpectedValues::Some(values)) = &sess.parse_sess.check_config.expecteds.get(&name) else {
784810
bug!("it shouldn't be possible to have a diagnostic on a value whose name is not in values");
785811
};
786-
let possibilities: Vec<Symbol> = values.iter().map(|&s| s).collect();
812+
let mut have_none_possibility = false;
813+
let possibilities: Vec<Symbol> = values.iter()
814+
.inspect(|a| have_none_possibility |= a.is_none())
815+
.copied()
816+
.flatten()
817+
.collect();
787818

788819
// Show the full list if all possible values for a given name, but don't do it
789820
// for names as the possibilities could be very long
@@ -792,17 +823,24 @@ pub trait LintContext: Sized {
792823
let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
793824
possibilities.sort();
794825

795-
let possibilities = possibilities.join(", ");
796-
db.note(format!("expected values for `{name}` are: {possibilities}"));
826+
let possibilities = possibilities.join("`, `");
827+
let none = if have_none_possibility { "(none), " } else { "" };
828+
829+
db.note(format!("expected values for `{name}` are: {none}`{possibilities}`"));
797830
}
798831

799-
// Suggest the most probable if we found one
800-
if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
801-
db.span_suggestion(value_span, "did you mean", format!("\"{best_match}\""), Applicability::MaybeIncorrect);
832+
if let Some((value, value_span)) = value {
833+
// Suggest the most probable if we found one
834+
if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
835+
db.span_suggestion(value_span, "there is a expected value with a similar name", format!("\"{best_match}\""), Applicability::MaybeIncorrect);
836+
837+
}
838+
} else if let &[first_possibility] = &possibilities[..] {
839+
db.span_suggestion(name_span.shrink_to_hi(), "specify a config value", format!(" = \"{first_possibility}\""), Applicability::MaybeIncorrect);
802840
}
803-
} else {
841+
} else if have_none_possibility {
804842
db.note(format!("no expected value for `{name}`"));
805-
if name != sym::feature {
843+
if let Some((_value, value_span)) = value {
806844
db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect);
807845
}
808846
}

compiler/rustc_lint_defs/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,8 @@ pub enum BuiltinLintDiagnostics {
496496
BreakWithLabelAndLoop(Span),
497497
NamedAsmLabel(String),
498498
UnicodeTextFlow(Span, String),
499-
UnexpectedCfg((Symbol, Span), Option<(Symbol, Span)>),
499+
UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>),
500+
UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>),
500501
DeprecatedWhereclauseLocation(Span, String),
501502
SingleUseLifetime {
502503
/// Span of the parameter which declares this lifetime.

0 commit comments

Comments
 (0)