Skip to content

Commit

Permalink
Rollup merge of #94175 - Urgau:check-cfg-improvements, r=petrochenkov
Browse files Browse the repository at this point in the history
Improve `--check-cfg` implementation

This pull-request is a mix of improvements regarding the `--check-cfg` implementation:

- Simpler internal representation (usage of `Option` instead of separate bool)
- Add --check-cfg to the unstable book (based on the RFC)
- Improved diagnostics:
    * List possible values when the value is unexpected
    * Suggest if possible a name or value that is similar
- Add more tests (well known names, mix of combinations, ...)

r? ```@petrochenkov```
  • Loading branch information
Dylan-DPC authored Feb 24, 2022
2 parents 7f99536 + a556a2a commit 000e38d
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 47 deletions.
41 changes: 25 additions & 16 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use rustc_errors::{struct_span_err, Applicability};
use rustc_feature::{find_gated_cfg, is_builtin_attr_name, Features, GatedCfg};
use rustc_macros::HashStable_Generic;
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_session::parse::{feature_err, ParseSess};
use rustc_session::Session;
use rustc_span::hygiene::Transparency;
Expand Down Expand Up @@ -461,29 +462,37 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
true
}
MetaItemKind::NameValue(..) | MetaItemKind::Word => {
let name = cfg.ident().expect("multi-segment cfg predicate").name;
let ident = cfg.ident().expect("multi-segment cfg predicate");
let name = ident.name;
let value = cfg.value_str();
if sess.check_config.names_checked && !sess.check_config.names_valid.contains(&name)
{
sess.buffer_lint(
UNEXPECTED_CFGS,
cfg.span,
CRATE_NODE_ID,
"unexpected `cfg` condition name",
);
}
if let Some(val) = value {
if sess.check_config.values_checked.contains(&name)
&& !sess.check_config.values_valid.contains(&(name, val))
{
sess.buffer_lint(
if let Some(names_valid) = &sess.check_config.names_valid {
if !names_valid.contains(&name) {
sess.buffer_lint_with_diagnostic(
UNEXPECTED_CFGS,
cfg.span,
CRATE_NODE_ID,
"unexpected `cfg` condition value",
"unexpected `cfg` condition name",
BuiltinLintDiagnostics::UnexpectedCfg(ident.span, name, None),
);
}
}
if let Some(value) = value {
if let Some(values) = &sess.check_config.values_valid.get(&name) {
if !values.contains(&value) {
sess.buffer_lint_with_diagnostic(
UNEXPECTED_CFGS,
cfg.span,
CRATE_NODE_ID,
"unexpected `cfg` condition value",
BuiltinLintDiagnostics::UnexpectedCfg(
cfg.name_value_literal_span().unwrap(),
name,
Some(value),
),
);
}
}
}
sess.config.contains(&(name, value))
}
}
Expand Down
18 changes: 12 additions & 6 deletions compiler/rustc_interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,12 @@ pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
Ok(meta_item) if parser.token == token::Eof => {
if let Some(args) = meta_item.meta_item_list() {
if meta_item.has_name(sym::names) {
cfg.names_checked = true;
let names_valid =
cfg.names_valid.get_or_insert_with(|| FxHashSet::default());
for arg in args {
if arg.is_word() && arg.ident().is_some() {
let ident = arg.ident().expect("multi-segment cfg key");
cfg.names_valid.insert(ident.name.to_string());
names_valid.insert(ident.name.to_string());
} else {
error!("`names()` arguments must be simple identifers");
}
Expand All @@ -183,13 +184,16 @@ pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
if let Some((name, values)) = args.split_first() {
if name.is_word() && name.ident().is_some() {
let ident = name.ident().expect("multi-segment cfg key");
cfg.values_checked.insert(ident.to_string());
let ident_values = cfg
.values_valid
.entry(ident.name.to_string())
.or_insert_with(|| FxHashSet::default());

for val in values {
if let Some(LitKind::Str(s, _)) =
val.literal().map(|lit| &lit.kind)
{
cfg.values_valid
.insert((ident.to_string(), s.to_string()));
ident_values.insert(s.to_string());
} else {
error!(
"`values()` arguments must be string literals"
Expand Down Expand Up @@ -219,7 +223,9 @@ pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
);
}

cfg.names_valid.extend(cfg.values_checked.iter().cloned());
if let Some(names_valid) = &mut cfg.names_valid {
names_valid.extend(cfg.values_valid.keys().cloned());
}
cfg
})
}
Expand Down
35 changes: 34 additions & 1 deletion compiler/rustc_lint/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,40 @@ pub trait LintContext: Sized {
BuiltinLintDiagnostics::NamedAsmLabel(help) => {
db.help(&help);
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");
}
},
BuiltinLintDiagnostics::UnexpectedCfg(span, name, value) => {
let possibilities: Vec<Symbol> = if value.is_some() {
let Some(values) = &sess.parse_sess.check_config.values_valid.get(&name) else {
bug!("it shouldn't be possible to have a diagnostic on a value whose name is not in values");
};
values.iter().map(|&s| s).collect()
} else {
let Some(names_valid) = &sess.parse_sess.check_config.names_valid else {
bug!("it shouldn't be possible to have a diagnostic on a name if name checking is not enabled");
};
names_valid.iter().map(|s| *s).collect()
};

// Show the full list if all possible values for a given name, but don't do it
// for names as the possibilities could be very long
if value.is_some() {
if !possibilities.is_empty() {
let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
possibilities.sort();

let possibilities = possibilities.join(", ");
db.note(&format!("expected values for `{name}` are: {possibilities}"));
} else {
db.note(&format!("no expected value for `{name}`"));
}
}

// Suggest the most probable if we found one
if let Some(best_match) = find_best_match_for_name(&possibilities, value.unwrap_or(name), None) {
let punctuation = if value.is_some() { "\"" } else { "" };
db.span_suggestion(span, "did you mean", format!("{punctuation}{best_match}{punctuation}"), Applicability::MaybeIncorrect);
}
},
}
// Rewrap `db`, and pass control to the user.
decorate(LintDiagnosticBuilder::new(db));
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 @@ -31,6 +31,7 @@
#![feature(box_patterns)]
#![feature(crate_visibility_modifier)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(iter_order_by)]
#![feature(let_else)]
#![feature(never_type)]
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ pub enum BuiltinLintDiagnostics {
BreakWithLabelAndLoop(Span),
NamedAsmLabel(String),
UnicodeTextFlow(Span, String),
UnexpectedCfg(Span, Symbol, Option<Symbol>),
}

/// Lints that are buffered up early on in the `Session` before the
Expand Down
48 changes: 25 additions & 23 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::search_paths::SearchPath;
use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind};
use crate::{early_error, early_warn, Session};

use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::impl_stable_hash_via_hash;

use rustc_target::abi::{Align, TargetDataLayout};
Expand Down Expand Up @@ -1023,34 +1023,30 @@ pub fn to_crate_config(cfg: FxHashSet<(String, Option<String>)>) -> CrateConfig

/// The parsed `--check-cfg` options
pub struct CheckCfg<T = String> {
/// Set if `names()` checking is enabled
pub names_checked: bool,
/// The union of all `names()`
pub names_valid: FxHashSet<T>,
/// The set of names for which `values()` was used
pub values_checked: FxHashSet<T>,
/// The set of all (name, value) pairs passed in `values()`
pub values_valid: FxHashSet<(T, T)>,
/// The set of all `names()`, if None no name checking is performed
pub names_valid: Option<FxHashSet<T>>,
/// The set of all `values()`
pub values_valid: FxHashMap<T, FxHashSet<T>>,
}

impl<T> Default for CheckCfg<T> {
fn default() -> Self {
CheckCfg {
names_checked: false,
names_valid: FxHashSet::default(),
values_checked: FxHashSet::default(),
values_valid: FxHashSet::default(),
}
CheckCfg { names_valid: Default::default(), values_valid: Default::default() }
}
}

impl<T> CheckCfg<T> {
fn map_data<O: Eq + Hash>(&self, f: impl Fn(&T) -> O) -> CheckCfg<O> {
CheckCfg {
names_checked: self.names_checked,
names_valid: self.names_valid.iter().map(|a| f(a)).collect(),
values_checked: self.values_checked.iter().map(|a| f(a)).collect(),
values_valid: self.values_valid.iter().map(|(a, b)| (f(a), f(b))).collect(),
names_valid: self
.names_valid
.as_ref()
.map(|names_valid| names_valid.iter().map(|a| f(a)).collect()),
values_valid: self
.values_valid
.iter()
.map(|(a, b)| (f(a), b.iter().map(|b| f(b)).collect()))
.collect(),
}
}
}
Expand Down Expand Up @@ -1090,17 +1086,23 @@ impl CrateCheckConfig {
sym::doctest,
sym::feature,
];
for &name in WELL_KNOWN_NAMES {
self.names_valid.insert(name);
if let Some(names_valid) = &mut self.names_valid {
for &name in WELL_KNOWN_NAMES {
names_valid.insert(name);
}
}
}

/// Fills a `CrateCheckConfig` with configuration names and values that are actually active.
pub fn fill_actual(&mut self, cfg: &CrateConfig) {
for &(k, v) in cfg {
self.names_valid.insert(k);
if let Some(names_valid) = &mut self.names_valid {
names_valid.insert(k);
}
if let Some(v) = v {
self.values_valid.insert((k, v));
self.values_valid.entry(k).and_modify(|values| {
values.insert(v);
});
}
}
}
Expand Down
Loading

0 comments on commit 000e38d

Please sign in to comment.