From e364f0eb5a51a96932ecd01dd2319eff4bdae2d1 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 13 Jul 2015 17:10:44 -0700 Subject: [PATCH] feature gate `cfg(target_feature)`. This is theoretically a breaking change, but GitHub search turns up no uses of it, and most non-built-in cfg's are passed via cargo features, which look like `feature = "..."`, and hence can't overlap. --- src/librustc_driver/driver.rs | 19 +++++- src/libsyntax/attr.rs | 15 +++-- src/libsyntax/config.rs | 28 ++++++--- src/libsyntax/ext/base.rs | 6 +- src/libsyntax/ext/cfg.rs | 3 +- src/libsyntax/ext/expand.rs | 14 +++-- src/libsyntax/feature_gate.rs | 60 +++++++++++++++++++ src/libsyntax/test.rs | 4 +- .../feature-gate-cfg-target-feature.rs | 21 +++++++ src/test/run-fail-fulldeps/qquote.rs | 4 +- src/test/run-pass-fulldeps/qquote.rs | 4 +- 11 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 src/test/compile-fail/feature-gate-cfg-target-feature.rs diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index 263a8e14807..346e7a7bf98 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -406,8 +406,10 @@ pub fn phase_2_configure_and_expand(sess: &Session, // // baz! should not use this definition unless foo is enabled. - krate = time(time_passes, "configuration 1", move || - syntax::config::strip_unconfigured_items(sess.diagnostic(), krate)); + let mut feature_gated_cfgs = vec![]; + krate = time(time_passes, "configuration 1", || + syntax::config::strip_unconfigured_items(sess.diagnostic(), krate, + &mut feature_gated_cfgs)); *sess.crate_types.borrow_mut() = collect_crate_types(sess, &krate.attrs); @@ -511,6 +513,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, cfg, macros, syntax_exts, + &mut feature_gated_cfgs, krate); if cfg!(windows) { env::set_var("PATH", &_old_path); @@ -536,7 +539,17 @@ pub fn phase_2_configure_and_expand(sess: &Session, // strip again, in case expansion added anything with a #[cfg]. krate = time(time_passes, "configuration 2", || - syntax::config::strip_unconfigured_items(sess.diagnostic(), krate)); + syntax::config::strip_unconfigured_items(sess.diagnostic(), krate, + &mut feature_gated_cfgs)); + + time(time_passes, "gated configuration checking", || { + let features = sess.features.borrow(); + feature_gated_cfgs.sort(); + feature_gated_cfgs.dedup(); + for cfg in &feature_gated_cfgs { + cfg.check_and_emit(sess.diagnostic(), &features); + } + }); krate = time(time_passes, "maybe building test harness", || syntax::test::modify_for_testing(&sess.parse_sess, diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index 3de9ba51974..7540c2ff831 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -19,6 +19,7 @@ use codemap::{Span, Spanned, spanned, dummy_spanned}; use codemap::BytePos; use diagnostic::SpanHandler; +use feature_gate::GatedCfg; use parse::lexer::comments::{doc_comment_style, strip_doc_comment_decoration}; use parse::token::{InternedString, intern_and_get_ident}; use parse::token; @@ -357,24 +358,28 @@ pub fn requests_inline(attrs: &[Attribute]) -> bool { } /// Tests if a cfg-pattern matches the cfg set -pub fn cfg_matches(diagnostic: &SpanHandler, cfgs: &[P], cfg: &ast::MetaItem) -> bool { +pub fn cfg_matches(diagnostic: &SpanHandler, cfgs: &[P], cfg: &ast::MetaItem, + feature_gated_cfgs: &mut Vec) -> bool { match cfg.node { ast::MetaList(ref pred, ref mis) if &pred[..] == "any" => - mis.iter().any(|mi| cfg_matches(diagnostic, cfgs, &**mi)), + mis.iter().any(|mi| cfg_matches(diagnostic, cfgs, &**mi, feature_gated_cfgs)), ast::MetaList(ref pred, ref mis) if &pred[..] == "all" => - mis.iter().all(|mi| cfg_matches(diagnostic, cfgs, &**mi)), + mis.iter().all(|mi| cfg_matches(diagnostic, cfgs, &**mi, feature_gated_cfgs)), ast::MetaList(ref pred, ref mis) if &pred[..] == "not" => { if mis.len() != 1 { diagnostic.span_err(cfg.span, "expected 1 cfg-pattern"); return false; } - !cfg_matches(diagnostic, cfgs, &*mis[0]) + !cfg_matches(diagnostic, cfgs, &*mis[0], feature_gated_cfgs) } ast::MetaList(ref pred, _) => { diagnostic.span_err(cfg.span, &format!("invalid predicate `{}`", pred)); false }, - ast::MetaWord(_) | ast::MetaNameValue(..) => contains(cfgs, cfg), + ast::MetaWord(_) | ast::MetaNameValue(..) => { + feature_gated_cfgs.extend(GatedCfg::gate(cfg)); + contains(cfgs, cfg) + } } } diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs index 366806bc19b..faf0b51c8de 100644 --- a/src/libsyntax/config.rs +++ b/src/libsyntax/config.rs @@ -10,6 +10,7 @@ use attr::AttrMetaMethods; use diagnostic::SpanHandler; +use feature_gate::GatedCfg; use fold::Folder; use {ast, fold, attr}; use codemap::{Spanned, respan}; @@ -25,10 +26,13 @@ struct Context where F: FnMut(&[ast::Attribute]) -> bool { // Support conditional compilation by transforming the AST, stripping out // any items that do not belong in the current configuration -pub fn strip_unconfigured_items(diagnostic: &SpanHandler, krate: ast::Crate) -> ast::Crate { - let krate = process_cfg_attr(diagnostic, krate); +pub fn strip_unconfigured_items(diagnostic: &SpanHandler, krate: ast::Crate, + feature_gated_cfgs: &mut Vec) + -> ast::Crate +{ + let krate = process_cfg_attr(diagnostic, krate, feature_gated_cfgs); let config = krate.config.clone(); - strip_items(krate, |attrs| in_cfg(diagnostic, &config, attrs)) + strip_items(krate, |attrs| in_cfg(diagnostic, &config, attrs, feature_gated_cfgs)) } impl fold::Folder for Context where F: FnMut(&[ast::Attribute]) -> bool { @@ -248,7 +252,8 @@ fn foreign_item_in_cfg(cx: &mut Context, item: &ast::ForeignItem) -> bool // Determine if an item should be translated in the current crate // configuration based on the item's attributes -fn in_cfg(diagnostic: &SpanHandler, cfg: &[P], attrs: &[ast::Attribute]) -> bool { +fn in_cfg(diagnostic: &SpanHandler, cfg: &[P], attrs: &[ast::Attribute], + feature_gated_cfgs: &mut Vec) -> bool { attrs.iter().all(|attr| { let mis = match attr.node.value.node { ast::MetaList(_, ref mis) if attr.check_name("cfg") => mis, @@ -260,25 +265,29 @@ fn in_cfg(diagnostic: &SpanHandler, cfg: &[P], attrs: &[ast::Attr return true; } - attr::cfg_matches(diagnostic, cfg, &*mis[0]) + attr::cfg_matches(diagnostic, cfg, &*mis[0], + feature_gated_cfgs) }) } -struct CfgAttrFolder<'a> { +struct CfgAttrFolder<'a, 'b> { diag: &'a SpanHandler, config: ast::CrateConfig, + feature_gated_cfgs: &'b mut Vec } // Process `#[cfg_attr]`. -fn process_cfg_attr(diagnostic: &SpanHandler, krate: ast::Crate) -> ast::Crate { +fn process_cfg_attr(diagnostic: &SpanHandler, krate: ast::Crate, + feature_gated_cfgs: &mut Vec) -> ast::Crate { let mut fld = CfgAttrFolder { diag: diagnostic, config: krate.config.clone(), + feature_gated_cfgs: feature_gated_cfgs, }; fld.fold_crate(krate) } -impl<'a> fold::Folder for CfgAttrFolder<'a> { +impl<'a,'b> fold::Folder for CfgAttrFolder<'a,'b> { fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { if !attr.check_name("cfg_attr") { return fold::noop_fold_attribute(attr, self); @@ -299,7 +308,8 @@ fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { } }; - if attr::cfg_matches(self.diag, &self.config[..], &cfg) { + if attr::cfg_matches(self.diag, &self.config[..], &cfg, + self.feature_gated_cfgs) { Some(respan(mi.span, ast::Attribute_ { id: attr::mk_attr_id(), style: attr.node.style, diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index d4b5e67eeb4..ef11a2bd66e 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -17,6 +17,7 @@ use ext; use ext::expand; use ext::tt::macro_rules; +use feature_gate::GatedCfg; use parse; use parse::parser; use parse::token; @@ -632,6 +633,7 @@ pub struct ExtCtxt<'a> { pub backtrace: ExpnId, pub ecfg: expand::ExpansionConfig<'a>, pub crate_root: Option<&'static str>, + pub feature_gated_cfgs: &'a mut Vec, pub mod_path: Vec , pub exported_macros: Vec, @@ -642,7 +644,8 @@ pub struct ExtCtxt<'a> { impl<'a> ExtCtxt<'a> { pub fn new(parse_sess: &'a parse::ParseSess, cfg: ast::CrateConfig, - ecfg: expand::ExpansionConfig<'a>) -> ExtCtxt<'a> { + ecfg: expand::ExpansionConfig<'a>, + feature_gated_cfgs: &'a mut Vec) -> ExtCtxt<'a> { let env = initial_syntax_expander_table(&ecfg); ExtCtxt { parse_sess: parse_sess, @@ -651,6 +654,7 @@ pub fn new(parse_sess: &'a parse::ParseSess, cfg: ast::CrateConfig, mod_path: Vec::new(), ecfg: ecfg, crate_root: None, + feature_gated_cfgs: feature_gated_cfgs, exported_macros: Vec::new(), syntax_env: env, recursion_count: 0, diff --git a/src/libsyntax/ext/cfg.rs b/src/libsyntax/ext/cfg.rs index 8af7fb7b268..aa654e30530 100644 --- a/src/libsyntax/ext/cfg.rs +++ b/src/libsyntax/ext/cfg.rs @@ -34,6 +34,7 @@ pub fn expand_cfg<'cx>(cx: &mut ExtCtxt, return DummyResult::expr(sp); } - let matches_cfg = attr::cfg_matches(&cx.parse_sess.span_diagnostic, &cx.cfg, &*cfg); + let matches_cfg = attr::cfg_matches(&cx.parse_sess.span_diagnostic, &cx.cfg, &*cfg, + cx.feature_gated_cfgs); MacEager::expr(cx.expr_bool(sp, matches_cfg)) } diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index e61a0b5401e..4f89b3494d4 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -21,7 +21,7 @@ use codemap; use codemap::{Span, Spanned, ExpnInfo, NameAndSpan, MacroBang, MacroAttribute, CompilerExpansion}; use ext::base::*; -use feature_gate::{self, Features}; +use feature_gate::{self, Features, GatedCfg}; use fold; use fold::*; use parse; @@ -1687,8 +1687,10 @@ pub fn expand_crate<'feat>(parse_sess: &parse::ParseSess, // these are the macros being imported to this crate: imported_macros: Vec, user_exts: Vec, + feature_gated_cfgs: &mut Vec, c: Crate) -> Crate { - let mut cx = ExtCtxt::new(parse_sess, c.config.clone(), cfg); + let mut cx = ExtCtxt::new(parse_sess, c.config.clone(), cfg, + feature_gated_cfgs); if std_inject::no_core(&c) { cx.crate_root = None; } else if std_inject::no_std(&c) { @@ -1878,7 +1880,7 @@ fn test_ecfg() -> ExpansionConfig<'static> { src, Vec::new(), &sess); // should fail: - expand_crate(&sess,test_ecfg(),vec!(),vec!(),crate_ast); + expand_crate(&sess,test_ecfg(),vec!(),vec!(), &mut vec![], crate_ast); } // make sure that macros can't escape modules @@ -1891,7 +1893,7 @@ fn test_ecfg() -> ExpansionConfig<'static> { "".to_string(), src, Vec::new(), &sess); - expand_crate(&sess,test_ecfg(),vec!(),vec!(),crate_ast); + expand_crate(&sess,test_ecfg(),vec!(),vec!(), &mut vec![], crate_ast); } // macro_use modules should allow macros to escape @@ -1903,14 +1905,14 @@ fn test_ecfg() -> ExpansionConfig<'static> { "".to_string(), src, Vec::new(), &sess); - expand_crate(&sess, test_ecfg(), vec!(), vec!(), crate_ast); + expand_crate(&sess, test_ecfg(), vec!(), vec!(), &mut vec![], crate_ast); } fn expand_crate_str(crate_str: String) -> ast::Crate { let ps = parse::ParseSess::new(); let crate_ast = panictry!(string_to_parser(&ps, crate_str).parse_crate_mod()); // the cfg argument actually does matter, here... - expand_crate(&ps,test_ecfg(),vec!(),vec!(),crate_ast) + expand_crate(&ps,test_ecfg(),vec!(),vec!(), &mut vec![], crate_ast) } // find the pat_ident paths in a crate diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index a12291161f7..9a1c97a4d29 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -37,6 +37,7 @@ use parse::token::{self, InternedString}; use std::ascii::AsciiExt; +use std::cmp; // If you change this list without updating src/doc/reference.md, @cmr will be sad // Don't ever remove anything from this list; set them to 'Removed'. @@ -180,6 +181,9 @@ // allow `repr(simd)`, and importing the various simd intrinsics ("simd_basics", "1.3.0", Active), + + // Allows cfg(target_feature = "..."). + ("cfg_target_feature", "1.3.0", Active), ]; // (changing above list without updating src/doc/reference.md makes @cmr sad) @@ -327,6 +331,59 @@ enum Status { ("recursion_limit", CrateLevel), ]; +macro_rules! cfg_fn { + (|$x: ident| $e: expr) => {{ + fn f($x: &Features) -> bool { + $e + } + f as fn(&Features) -> bool + }} +} +// cfg(...)'s that are feature gated +const GATED_CFGS: &'static [(&'static str, &'static str, fn(&Features) -> bool)] = &[ + // (name in cfg, feature, function to check if the feature is enabled) + ("target_feature", "cfg_target_feature", cfg_fn!(|x| x.cfg_target_feature)), +]; + +#[derive(Debug, Eq, PartialEq)] +pub struct GatedCfg { + span: Span, + index: usize, +} +impl Ord for GatedCfg { + fn cmp(&self, other: &GatedCfg) -> cmp::Ordering { + (self.span.lo.0, self.span.hi.0, self.index) + .cmp(&(other.span.lo.0, other.span.hi.0, other.index)) + } +} +impl PartialOrd for GatedCfg { + fn partial_cmp(&self, other: &GatedCfg) -> Option { + Some(self.cmp(other)) + } +} + +impl GatedCfg { + pub fn gate(cfg: &ast::MetaItem) -> Option { + let name = cfg.name(); + GATED_CFGS.iter() + .position(|info| info.0 == name) + .map(|idx| { + GatedCfg { + span: cfg.span, + index: idx + } + }) + } + pub fn check_and_emit(&self, diagnostic: &SpanHandler, features: &Features) { + let (cfg, feature, has_feature) = GATED_CFGS[self.index]; + if !has_feature(features) { + let explain = format!("`cfg({})` is experimental and subject to change", cfg); + emit_feature_err(diagnostic, feature, self.span, &explain); + } + } +} + + #[derive(PartialEq, Copy, Clone, Debug)] pub enum AttributeType { /// Normal, builtin attribute that is consumed @@ -373,6 +430,7 @@ pub struct Features { pub static_recursion: bool, pub default_type_parameter_fallback: bool, pub type_macros: bool, + pub cfg_target_feature: bool, } impl Features { @@ -401,6 +459,7 @@ pub fn new() -> Features { static_recursion: false, default_type_parameter_fallback: false, type_macros: false, + cfg_target_feature: false, } } } @@ -920,6 +979,7 @@ fn check_crate_inner(cm: &CodeMap, span_handler: &SpanHandler, static_recursion: cx.has_feature("static_recursion"), default_type_parameter_fallback: cx.has_feature("default_type_parameter_fallback"), type_macros: cx.has_feature("type_macros"), + cfg_target_feature: cx.has_feature("cfg_target_feature"), } } diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index ea99291d6c2..26fb287ce35 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -246,11 +246,13 @@ fn generate_test_harness(sess: &ParseSess, krate: ast::Crate, cfg: &ast::CrateConfig, sd: &diagnostic::SpanHandler) -> ast::Crate { + let mut feature_gated_cfgs = vec![]; let mut cx: TestCtxt = TestCtxt { sess: sess, span_diagnostic: sd, ext_cx: ExtCtxt::new(sess, cfg.clone(), - ExpansionConfig::default("test".to_string())), + ExpansionConfig::default("test".to_string()), + &mut feature_gated_cfgs), path: Vec::new(), testfns: Vec::new(), reexport_test_harness_main: reexport_test_harness_main, diff --git a/src/test/compile-fail/feature-gate-cfg-target-feature.rs b/src/test/compile-fail/feature-gate-cfg-target-feature.rs new file mode 100644 index 00000000000..7832e1c7c51 --- /dev/null +++ b/src/test/compile-fail/feature-gate-cfg-target-feature.rs @@ -0,0 +1,21 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[cfg(target_feature = "x")] //~ ERROR `cfg(target_feature)` is experimental +#[cfg_attr(target_feature = "x", x)] //~ ERROR `cfg(target_feature)` is experimental +struct Foo(u64, u64); + +#[cfg(not(any(all(target_feature = "x"))))] //~ ERROR `cfg(target_feature)` is experimental +fn foo() {} + +fn main() { + cfg!(target_feature = "x"); + //~^ ERROR `cfg(target_feature)` is experimental and subject to change +} diff --git a/src/test/run-fail-fulldeps/qquote.rs b/src/test/run-fail-fulldeps/qquote.rs index 4251579bbdc..eac38037b4b 100644 --- a/src/test/run-fail-fulldeps/qquote.rs +++ b/src/test/run-fail-fulldeps/qquote.rs @@ -23,9 +23,11 @@ fn main() { let ps = syntax::parse::ParseSess::new(); + let mut feature_gated_cfgs = vec![]; let mut cx = syntax::ext::base::ExtCtxt::new( &ps, vec![], - syntax::ext::expand::ExpansionConfig::default("qquote".to_string())); + syntax::ext::expand::ExpansionConfig::default("qquote".to_string()), + &mut feature_gated_cfgs); cx.bt_push(syntax::codemap::ExpnInfo { call_site: DUMMY_SP, callee: syntax::codemap::NameAndSpan { diff --git a/src/test/run-pass-fulldeps/qquote.rs b/src/test/run-pass-fulldeps/qquote.rs index 6670f200ba7..e272a5fe4f6 100644 --- a/src/test/run-pass-fulldeps/qquote.rs +++ b/src/test/run-pass-fulldeps/qquote.rs @@ -19,9 +19,11 @@ fn main() { let ps = syntax::parse::ParseSess::new(); + let mut feature_gated_cfgs = vec![]; let mut cx = syntax::ext::base::ExtCtxt::new( &ps, vec![], - syntax::ext::expand::ExpansionConfig::default("qquote".to_string())); + syntax::ext::expand::ExpansionConfig::default("qquote".to_string()), + &mut feature_gated_cfgs); cx.bt_push(syntax::codemap::ExpnInfo { call_site: DUMMY_SP, callee: syntax::codemap::NameAndSpan { -- 2.44.0