Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve --check-cfg implementation #94175

Merged
merged 5 commits into from
Feb 25, 2022
Merged
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
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 @@ -766,7 +766,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