Skip to content

Commit 576afec

Browse files
authored
Rollup merge of rust-lang#93915 - Urgau:rfc-3013, r=petrochenkov
Implement --check-cfg option (RFC 3013), take 2 This pull-request implement RFC 3013: Checking conditional compilation at compile time (rust-lang/rfcs#3013) and is based on the previous attempt rust-lang#89346 by `@mwkmwkmwk` that was closed due to inactivity. I have address all the review comments from the previous attempt and added some more tests. cc rust-lang#82450 r? `@petrochenkov`
2 parents 1e2f63d + 3a73ca5 commit 576afec

27 files changed

+365
-7
lines changed

compiler/rustc_attr/src/builtin.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
//! Parsing and validation of builtin attributes
22
3-
use rustc_ast::{self as ast, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem};
3+
use rustc_ast as ast;
4+
use rustc_ast::node_id::CRATE_NODE_ID;
5+
use rustc_ast::{Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem};
46
use rustc_ast_pretty::pprust;
57
use rustc_errors::{struct_span_err, Applicability};
68
use rustc_feature::{find_gated_cfg, is_builtin_attr_name, Features, GatedCfg};
79
use rustc_macros::HashStable_Generic;
10+
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
811
use rustc_session::parse::{feature_err, ParseSess};
912
use rustc_session::Session;
1013
use rustc_span::hygiene::Transparency;
@@ -458,8 +461,30 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
458461
true
459462
}
460463
MetaItemKind::NameValue(..) | MetaItemKind::Word => {
461-
let ident = cfg.ident().expect("multi-segment cfg predicate");
462-
sess.config.contains(&(ident.name, cfg.value_str()))
464+
let name = cfg.ident().expect("multi-segment cfg predicate").name;
465+
let value = cfg.value_str();
466+
if sess.check_config.names_checked && !sess.check_config.names_valid.contains(&name)
467+
{
468+
sess.buffer_lint(
469+
UNEXPECTED_CFGS,
470+
cfg.span,
471+
CRATE_NODE_ID,
472+
"unexpected `cfg` condition name",
473+
);
474+
}
475+
if let Some(val) = value {
476+
if sess.check_config.values_checked.contains(&name)
477+
&& !sess.check_config.values_valid.contains(&(name, val))
478+
{
479+
sess.buffer_lint(
480+
UNEXPECTED_CFGS,
481+
cfg.span,
482+
CRATE_NODE_ID,
483+
"unexpected `cfg` condition value",
484+
);
485+
}
486+
}
487+
sess.config.contains(&(name, value))
463488
}
464489
}
465490
})

compiler/rustc_driver/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,12 @@ fn run_compiler(
216216
}
217217

218218
let cfg = interface::parse_cfgspecs(matches.opt_strs("cfg"));
219+
let check_cfg = interface::parse_check_cfg(matches.opt_strs("check-cfg"));
219220
let (odir, ofile) = make_output(&matches);
220221
let mut config = interface::Config {
221222
opts: sopts,
222223
crate_cfg: cfg,
224+
crate_check_cfg: check_cfg,
223225
input: Input::File(PathBuf::new()),
224226
input_path: None,
225227
output_file: ofile,

compiler/rustc_interface/src/interface.rs

+89-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pub use crate::passes::BoxedResolver;
22
use crate::util;
33

44
use rustc_ast::token;
5-
use rustc_ast::{self as ast, MetaItemKind};
5+
use rustc_ast::{self as ast, LitKind, MetaItemKind};
66
use rustc_codegen_ssa::traits::CodegenBackend;
77
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
88
use rustc_data_structures::sync::Lrc;
@@ -13,12 +13,13 @@ use rustc_lint::LintStore;
1313
use rustc_middle::ty;
1414
use rustc_parse::maybe_new_parser_from_source_str;
1515
use rustc_query_impl::QueryCtxt;
16-
use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames};
16+
use rustc_session::config::{self, CheckCfg, ErrorOutputType, Input, OutputFilenames};
1717
use rustc_session::early_error;
1818
use rustc_session::lint;
1919
use rustc_session::parse::{CrateConfig, ParseSess};
2020
use rustc_session::{DiagnosticOutput, Session};
2121
use rustc_span::source_map::{FileLoader, FileName};
22+
use rustc_span::symbol::sym;
2223
use std::path::PathBuf;
2324
use std::result;
2425

@@ -139,13 +140,98 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String
139140
})
140141
}
141142

143+
/// Converts strings provided as `--check-cfg [specs]` into a `CheckCfg`.
144+
pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
145+
rustc_span::create_default_session_if_not_set_then(move |_| {
146+
let mut cfg = CheckCfg::default();
147+
148+
'specs: for s in specs {
149+
let sess = ParseSess::with_silent_emitter(Some(format!(
150+
"this error occurred on the command line: `--check-cfg={}`",
151+
s
152+
)));
153+
let filename = FileName::cfg_spec_source_code(&s);
154+
155+
macro_rules! error {
156+
($reason: expr) => {
157+
early_error(
158+
ErrorOutputType::default(),
159+
&format!(
160+
concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"),
161+
s
162+
),
163+
);
164+
};
165+
}
166+
167+
match maybe_new_parser_from_source_str(&sess, filename, s.to_string()) {
168+
Ok(mut parser) => match &mut parser.parse_meta_item() {
169+
Ok(meta_item) if parser.token == token::Eof => {
170+
if let Some(args) = meta_item.meta_item_list() {
171+
if meta_item.has_name(sym::names) {
172+
cfg.names_checked = true;
173+
for arg in args {
174+
if arg.is_word() && arg.ident().is_some() {
175+
let ident = arg.ident().expect("multi-segment cfg key");
176+
cfg.names_valid.insert(ident.name.to_string());
177+
} else {
178+
error!("`names()` arguments must be simple identifers");
179+
}
180+
}
181+
continue 'specs;
182+
} else if meta_item.has_name(sym::values) {
183+
if let Some((name, values)) = args.split_first() {
184+
if name.is_word() && name.ident().is_some() {
185+
let ident = name.ident().expect("multi-segment cfg key");
186+
cfg.values_checked.insert(ident.to_string());
187+
for val in values {
188+
if let Some(LitKind::Str(s, _)) =
189+
val.literal().map(|lit| &lit.kind)
190+
{
191+
cfg.values_valid
192+
.insert((ident.to_string(), s.to_string()));
193+
} else {
194+
error!(
195+
"`values()` arguments must be string literals"
196+
);
197+
}
198+
}
199+
200+
continue 'specs;
201+
} else {
202+
error!(
203+
"`values()` first argument must be a simple identifer"
204+
);
205+
}
206+
}
207+
}
208+
}
209+
}
210+
Ok(..) => {}
211+
Err(err) => err.cancel(),
212+
},
213+
Err(errs) => errs.into_iter().for_each(|mut err| err.cancel()),
214+
}
215+
216+
error!(
217+
"expected `names(name1, name2, ... nameN)` or \
218+
`values(name, \"value1\", \"value2\", ... \"valueN\")`"
219+
);
220+
}
221+
222+
cfg.names_valid.extend(cfg.values_checked.iter().cloned());
223+
cfg
224+
})
225+
}
226+
142227
/// The compiler configuration
143228
pub struct Config {
144229
/// Command line options
145230
pub opts: config::Options,
146231

147232
/// cfg! configuration in addition to the default ones
148233
pub crate_cfg: FxHashSet<(String, Option<String>)>,
234+
pub crate_check_cfg: CheckCfg,
149235

150236
pub input: Input,
151237
pub input_path: Option<PathBuf>,
@@ -188,6 +274,7 @@ pub fn create_compiler_and_run<R>(config: Config, f: impl FnOnce(&Compiler) -> R
188274
let (mut sess, codegen_backend) = util::create_session(
189275
config.opts,
190276
config.crate_cfg,
277+
config.crate_check_cfg,
191278
config.diagnostic_output,
192279
config.file_loader,
193280
config.input_path.clone(),

compiler/rustc_interface/src/util.rs

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc_parse::validate_attr;
1515
use rustc_query_impl::QueryCtxt;
1616
use rustc_resolve::{self, Resolver};
1717
use rustc_session as session;
18+
use rustc_session::config::CheckCfg;
1819
use rustc_session::config::{self, CrateType};
1920
use rustc_session::config::{ErrorOutputType, Input, OutputFilenames};
2021
use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
@@ -65,6 +66,7 @@ pub fn add_configuration(
6566
pub fn create_session(
6667
sopts: config::Options,
6768
cfg: FxHashSet<(String, Option<String>)>,
69+
check_cfg: CheckCfg,
6870
diagnostic_output: DiagnosticOutput,
6971
file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>,
7072
input_path: Option<PathBuf>,
@@ -100,7 +102,13 @@ pub fn create_session(
100102

101103
let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg));
102104
add_configuration(&mut cfg, &mut sess, &*codegen_backend);
105+
106+
let mut check_cfg = config::to_crate_check_config(check_cfg);
107+
check_cfg.fill_well_known();
108+
check_cfg.fill_actual(&cfg);
109+
103110
sess.parse_sess.config = cfg;
111+
sess.parse_sess.check_config = check_cfg;
104112

105113
(Lrc::new(sess), Lrc::new(codegen_backend))
106114
}

compiler/rustc_lint_defs/src/builtin.rs

+38
Original file line numberDiff line numberDiff line change
@@ -2957,6 +2957,43 @@ declare_lint! {
29572957
};
29582958
}
29592959

2960+
declare_lint! {
2961+
/// The `unexpected_cfgs` lint detects unexpected conditional compilation conditions.
2962+
///
2963+
/// ### Example
2964+
///
2965+
/// ```text
2966+
/// rustc --check-cfg 'names()'
2967+
/// ```
2968+
///
2969+
/// ```rust,ignore (needs command line option)
2970+
/// #[cfg(widnows)]
2971+
/// fn foo() {}
2972+
/// ```
2973+
///
2974+
/// This will produce:
2975+
///
2976+
/// ```text
2977+
/// warning: unknown condition name used
2978+
/// --> lint_example.rs:1:7
2979+
/// |
2980+
/// 1 | #[cfg(widnows)]
2981+
/// | ^^^^^^^
2982+
/// |
2983+
/// = note: `#[warn(unexpected_cfgs)]` on by default
2984+
/// ```
2985+
///
2986+
/// ### Explanation
2987+
///
2988+
/// This lint is only active when a `--check-cfg='names(...)'` option has been passed
2989+
/// to the compiler and triggers whenever an unknown condition name or value is used.
2990+
/// The known condition include names or values passed in `--check-cfg`, `--cfg`, and some
2991+
/// well-knows names and values built into the compiler.
2992+
pub UNEXPECTED_CFGS,
2993+
Warn,
2994+
"detects unexpected names and values in `#[cfg]` conditions",
2995+
}
2996+
29602997
declare_lint_pass! {
29612998
/// Does nothing as a lint pass, but registers some `Lint`s
29622999
/// that are used by other parts of the compiler.
@@ -3055,6 +3092,7 @@ declare_lint_pass! {
30553092
DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
30563093
DUPLICATE_MACRO_ATTRIBUTES,
30573094
SUSPICIOUS_AUTO_TRAIT_IMPLS,
3095+
UNEXPECTED_CFGS,
30583096
]
30593097
}
30603098

compiler/rustc_session/src/config.rs

+88-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_target::spec::{LinkerFlavor, SplitDebuginfo, Target, TargetTriple, Tar
1616

1717
use rustc_serialize::json;
1818

19-
use crate::parse::CrateConfig;
19+
use crate::parse::{CrateCheckConfig, CrateConfig};
2020
use rustc_feature::UnstableFeatures;
2121
use rustc_span::edition::{Edition, DEFAULT_EDITION, EDITION_NAME_LIST, LATEST_STABLE_EDITION};
2222
use rustc_span::source_map::{FileName, FilePathMapping};
@@ -936,6 +936,7 @@ pub const fn default_lib_output() -> CrateType {
936936
}
937937

938938
fn default_configuration(sess: &Session) -> CrateConfig {
939+
// NOTE: This should be kept in sync with `CrateCheckConfig::fill_well_known` below.
939940
let end = &sess.target.endian;
940941
let arch = &sess.target.arch;
941942
let wordsz = sess.target.pointer_width.to_string();
@@ -1020,6 +1021,91 @@ pub fn to_crate_config(cfg: FxHashSet<(String, Option<String>)>) -> CrateConfig
10201021
cfg.into_iter().map(|(a, b)| (Symbol::intern(&a), b.map(|b| Symbol::intern(&b)))).collect()
10211022
}
10221023

1024+
/// The parsed `--check-cfg` options
1025+
pub struct CheckCfg<T = String> {
1026+
/// Set if `names()` checking is enabled
1027+
pub names_checked: bool,
1028+
/// The union of all `names()`
1029+
pub names_valid: FxHashSet<T>,
1030+
/// The set of names for which `values()` was used
1031+
pub values_checked: FxHashSet<T>,
1032+
/// The set of all (name, value) pairs passed in `values()`
1033+
pub values_valid: FxHashSet<(T, T)>,
1034+
}
1035+
1036+
impl<T> Default for CheckCfg<T> {
1037+
fn default() -> Self {
1038+
CheckCfg {
1039+
names_checked: false,
1040+
names_valid: FxHashSet::default(),
1041+
values_checked: FxHashSet::default(),
1042+
values_valid: FxHashSet::default(),
1043+
}
1044+
}
1045+
}
1046+
1047+
impl<T> CheckCfg<T> {
1048+
fn map_data<O: Eq + Hash>(&self, f: impl Fn(&T) -> O) -> CheckCfg<O> {
1049+
CheckCfg {
1050+
names_checked: self.names_checked,
1051+
names_valid: self.names_valid.iter().map(|a| f(a)).collect(),
1052+
values_checked: self.values_checked.iter().map(|a| f(a)).collect(),
1053+
values_valid: self.values_valid.iter().map(|(a, b)| (f(a), f(b))).collect(),
1054+
}
1055+
}
1056+
}
1057+
1058+
/// Converts the crate `--check-cfg` options from `String` to `Symbol`.
1059+
/// `rustc_interface::interface::Config` accepts this in the compiler configuration,
1060+
/// but the symbol interner is not yet set up then, so we must convert it later.
1061+
pub fn to_crate_check_config(cfg: CheckCfg) -> CrateCheckConfig {
1062+
cfg.map_data(|s| Symbol::intern(s))
1063+
}
1064+
1065+
impl CrateCheckConfig {
1066+
/// Fills a `CrateCheckConfig` with well-known configuration names.
1067+
pub fn fill_well_known(&mut self) {
1068+
// NOTE: This should be kept in sync with `default_configuration`
1069+
const WELL_KNOWN_NAMES: &[Symbol] = &[
1070+
sym::unix,
1071+
sym::windows,
1072+
sym::target_os,
1073+
sym::target_family,
1074+
sym::target_arch,
1075+
sym::target_endian,
1076+
sym::target_pointer_width,
1077+
sym::target_env,
1078+
sym::target_abi,
1079+
sym::target_vendor,
1080+
sym::target_thread_local,
1081+
sym::target_has_atomic_load_store,
1082+
sym::target_has_atomic,
1083+
sym::target_has_atomic_equal_alignment,
1084+
sym::panic,
1085+
sym::sanitize,
1086+
sym::debug_assertions,
1087+
sym::proc_macro,
1088+
sym::test,
1089+
sym::doc,
1090+
sym::doctest,
1091+
sym::feature,
1092+
];
1093+
for &name in WELL_KNOWN_NAMES {
1094+
self.names_valid.insert(name);
1095+
}
1096+
}
1097+
1098+
/// Fills a `CrateCheckConfig` with configuration names and values that are actually active.
1099+
pub fn fill_actual(&mut self, cfg: &CrateConfig) {
1100+
for &(k, v) in cfg {
1101+
self.names_valid.insert(k);
1102+
if let Some(v) = v {
1103+
self.values_valid.insert((k, v));
1104+
}
1105+
}
1106+
}
1107+
}
1108+
10231109
pub fn build_configuration(sess: &Session, mut user_cfg: CrateConfig) -> CrateConfig {
10241110
// Combine the configuration requested by the session (command line) with
10251111
// some default and generated configuration items.
@@ -1163,6 +1249,7 @@ pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
11631249
vec![
11641250
opt::flag_s("h", "help", "Display this message"),
11651251
opt::multi_s("", "cfg", "Configure the compilation environment", "SPEC"),
1252+
opt::multi("", "check-cfg", "Provide list of valid cfg options for checking", "SPEC"),
11661253
opt::multi_s(
11671254
"L",
11681255
"",

0 commit comments

Comments
 (0)