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

Implement --check-cfg option (RFC 3013) #89346

Closed
wants to merge 3 commits into from
Closed
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
31 changes: 29 additions & 2 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
//! Parsing and validation of builtin attributes

use rustc_ast::{self as ast, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem};
use rustc_ast::{
self as ast, node_id::CRATE_NODE_ID, Attribute, Lit, LitKind, MetaItem, MetaItemKind,
NestedMetaItem,
};
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
use rustc_ast_pretty::pprust;
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::{INVALID_CFG_NAME, INVALID_CFG_VALUE};
use rustc_session::parse::{feature_err, ParseSess};
use rustc_session::Session;
use rustc_span::hygiene::Transparency;
Expand Down Expand Up @@ -463,7 +467,30 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat
}
MetaItemKind::NameValue(..) | MetaItemKind::Word => {
let ident = cfg.ident().expect("multi-segment cfg predicate");
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
sess.config.contains(&(ident.name, cfg.value_str()))
let value = cfg.value_str();
if sess.check_config.names_checked
&& !sess.check_config.names_valid.contains(&ident.name)
{
sess.buffer_lint(
INVALID_CFG_NAME,
cfg.span,
CRATE_NODE_ID,
"unknown condition name used",
);
}
if let Some(val) = value {
if sess.check_config.values_checked.contains(&ident.name)
&& !sess.check_config.values_valid.contains(&(ident.name, val))
{
sess.buffer_lint(
INVALID_CFG_VALUE,
cfg.span,
CRATE_NODE_ID,
"unknown condition value used",
);
}
}
sess.config.contains(&(ident.name, value))
}
}
})
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,12 @@ fn run_compiler(
}

let cfg = interface::parse_cfgspecs(matches.opt_strs("cfg"));
let check_cfg = interface::parse_check_cfg(matches.opt_strs("check-cfg"));
let (odir, ofile) = make_output(&matches);
let mut config = interface::Config {
opts: sopts,
crate_cfg: cfg,
crate_check_cfg: check_cfg,
input: Input::File(PathBuf::new()),
input_path: None,
output_file: ofile,
Expand Down
82 changes: 80 additions & 2 deletions compiler/rustc_interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub use crate::passes::BoxedResolver;
use crate::util;

use rustc_ast::token;
use rustc_ast::{self as ast, MetaItemKind};
use rustc_ast::{self as ast, LitKind, MetaItemKind};
use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::sync::Lrc;
Expand All @@ -13,12 +13,13 @@ use rustc_lint::LintStore;
use rustc_middle::ty;
use rustc_parse::new_parser_from_source_str;
use rustc_query_impl::QueryCtxt;
use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames};
use rustc_session::config::{self, CheckCfg, ErrorOutputType, Input, OutputFilenames};
use rustc_session::early_error;
use rustc_session::lint;
use rustc_session::parse::{CrateConfig, ParseSess};
use rustc_session::{DiagnosticOutput, Session};
use rustc_span::source_map::{FileLoader, FileName};
use rustc_span::symbol::sym;
use std::path::PathBuf;
use std::result;
use std::sync::{Arc, Mutex};
Expand Down Expand Up @@ -123,13 +124,89 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String
})
}

/// Converts strings provided as `--check-cfg [spec]` into a `CheckCfg`.
pub fn parse_check_cfg(specs: Vec<String>) -> CheckCfg {
rustc_span::create_default_session_if_not_set_then(move |_| {
let mut cfg = CheckCfg {
names_checked: false,
names_valid: Default::default(),
values_checked: Default::default(),
values_valid: Default::default(),
};
for s in specs {
let sess = ParseSess::with_silent_emitter();
let filename = FileName::cfg_spec_source_code(&s);
let mut parser = new_parser_from_source_str(&sess, filename, s.to_string());

macro_rules! error {
($reason: expr) => {
early_error(
ErrorOutputType::default(),
&format!(
concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"),
s
),
);
};
}

match &mut parser.parse_meta_item() {
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;
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());
} else {
error!("`names()` arguments must be simple identifers");
}
}
continue;
} else if meta_item.has_name(sym::values) {
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());
for val in values {
if let Some(lit) = val.literal() {
if let LitKind::Str(s, _) = lit.kind {
cfg.values_valid
.insert((ident.to_string(), s.to_string()));
continue;
}
}
error!("`values()` arguments must be string literals");
}
} else {
error!("`values()` first argument must be a simple identifer");
}
continue;
}
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
}
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
}
}
Ok(..) => {}
Err(err) => err.cancel(),
}

error!(
r#"expected `names(name1, name2, ... nameN)` or `values(name, "value1", "value2", ... "valueN")`"#
);
}
cfg
})
}

/// The compiler configuration
pub struct Config {
/// Command line options
pub opts: config::Options,

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

pub input: Input,
pub input_path: Option<PathBuf>,
Expand Down Expand Up @@ -173,6 +250,7 @@ pub fn create_compiler_and_run<R>(config: Config, f: impl FnOnce(&Compiler) -> R
let (mut sess, codegen_backend) = util::create_session(
config.opts,
config.crate_cfg,
config.crate_check_cfg,
config.diagnostic_output,
config.file_loader,
config.input_path.clone(),
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_interface/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use rustc_parse::validate_attr;
use rustc_query_impl::QueryCtxt;
use rustc_resolve::{self, Resolver};
use rustc_session as session;
use rustc_session::config::CheckCfg;
use rustc_session::config::{self, CrateType};
use rustc_session::config::{ErrorOutputType, Input, OutputFilenames};
use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
Expand Down Expand Up @@ -64,6 +65,7 @@ pub fn add_configuration(
pub fn create_session(
sopts: config::Options,
cfg: FxHashSet<(String, Option<String>)>,
check_cfg: CheckCfg,
diagnostic_output: DiagnosticOutput,
file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>,
input_path: Option<PathBuf>,
Expand Down Expand Up @@ -99,7 +101,11 @@ pub fn create_session(

let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg));
add_configuration(&mut cfg, &mut sess, &*codegen_backend);
let mut check_cfg = config::to_check_config(check_cfg);
config::fill_check_config_well_known(&mut check_cfg);
config::fill_check_config_actual(&mut check_cfg, &cfg);
sess.parse_sess.config = cfg;
sess.parse_sess.check_config = check_cfg;

(Lrc::new(sess), Lrc::new(codegen_backend))
}
Expand Down
76 changes: 76 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2956,6 +2956,80 @@ declare_lint! {
"detects large moves or copies",
}

declare_lint! {
/// The `invalid_cfg_name` lint detects invalid conditional compilation conditions.
///
/// ### Example
///
/// ```text
/// rustc --check-cfg 'names()'
/// ```
///
/// ```rust,ignore (needs command line option)
/// #[cfg(widnows)]
/// fn foo() {}
/// ```
///
/// This will produce:
///
/// ```text
/// warning: unknown condition name used
/// --> lint_example.rs:1:7
/// |
/// 1 | #[cfg(widnows)]
/// | ^^^^^^^
/// |
/// = note: `#[warn(invalid_cfg_name)]` on by default
/// ```
///
/// ### Explanation
///
/// This lint is only active when a `--check-cfg='names(...)'` option has been passed
/// to the compiler and triggers whenever an unknown condition name is used. The known
/// condition names include names passed in `--check-cfg`, `--cfg`, and some well-known
/// names built into the compiler.
pub INVALID_CFG_NAME,
Warn,
"detects invalid #[cfg] condition names",
}

declare_lint! {
/// The `invalid_cfg_value` lint detects invalid conditional compilation conditions.
///
/// ### Example
///
/// ```text
/// rustc --check-cfg 'values(feature, "serde")'
/// ```
///
/// ```rust,ignore (needs command line option)
/// #[cfg(feature = "sedre"))]
/// fn foo() {}
/// ```
///
/// This will produce:
///
/// ```text
/// warning: unknown condition value used
/// --> lint_example.rs:1:7
/// |
/// 1 | #[cfg(feature = "sedre")]
/// | ^^^^^^^^^^^^^^^^^
/// |
/// = note: `#[warn(invalid_cfg_value)]` on by default
/// ```
///
/// ### Explanation
///
/// This lint is only active when a `--check-cfg='values(...)'` option has been passed
/// to the compiler and triggers whenever an unknown condition value is used for the
/// given name. The known condition values include values passed in `--check-cfg`
/// and `--cfg`.
pub INVALID_CFG_VALUE,
Warn,
"detects invalid #[cfg] condition values",
}

declare_lint_pass! {
/// Does nothing as a lint pass, but registers some `Lint`s
/// that are used by other parts of the compiler.
Expand Down Expand Up @@ -3050,6 +3124,8 @@ declare_lint_pass! {
BREAK_WITH_LABEL_AND_LOOP,
UNUSED_ATTRIBUTES,
NON_EXHAUSTIVE_OMITTED_PATTERNS,
INVALID_CFG_NAME,
INVALID_CFG_VALUE,
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
]
}

Expand Down
Loading