diff --git a/Cargo.lock b/Cargo.lock index 8bd1e5f3308..0fed32f4a9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3593,6 +3593,7 @@ dependencies = [ "rustc_session", "rustc_span", "serialize", + "version_check", ] [[package]] diff --git a/src/doc/unstable-book/src/language-features/cfg-version.md b/src/doc/unstable-book/src/language-features/cfg-version.md new file mode 100644 index 00000000000..2b1e50835b7 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/cfg-version.md @@ -0,0 +1,34 @@ +# `cfg_version` + +The tracking issue for this feature is: [#64796] + +[#64796]: https://github.com/rust-lang/rust/issues/64796 + +------------------------ + +The `cfg_version` feature makes it possible to execute different code +depending on the compiler version. + +## Examples + +```rust +#![feature(cfg_version)] + +#[cfg(version("1.42"))] +fn a() { + // ... +} + +#[cfg(not(version("1.42")))] +fn a() { + // ... +} + +fn b() { + if cfg!(version("1.42")) { + // ... + } else { + // ... + } +} +``` diff --git a/src/librustc_attr/Cargo.toml b/src/librustc_attr/Cargo.toml index a7a7e3dcc5f..ad97ac8ffd3 100644 --- a/src/librustc_attr/Cargo.toml +++ b/src/librustc_attr/Cargo.toml @@ -19,3 +19,4 @@ rustc_feature = { path = "../librustc_feature" } rustc_macros = { path = "../librustc_macros" } rustc_session = { path = "../librustc_session" } rustc_ast = { path = "../librustc_ast" } +version_check = "0.9" diff --git a/src/librustc_attr/builtin.rs b/src/librustc_attr/builtin.rs index 0a6a4821e27..c29d8c0e0fe 100644 --- a/src/librustc_attr/builtin.rs +++ b/src/librustc_attr/builtin.rs @@ -2,7 +2,7 @@ use super::{find_by_name, mark_used}; -use rustc_ast::ast::{self, Attribute, MetaItem, MetaItemKind, NestedMetaItem}; +use rustc_ast::ast::{self, Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem}; use rustc_ast_pretty::pprust; use rustc_errors::{struct_span_err, Applicability, Handler}; use rustc_feature::{find_gated_cfg, is_builtin_attr_name, Features, GatedCfg}; @@ -11,6 +11,7 @@ use rustc_session::parse::{feature_err, ParseSess}; use rustc_span::hygiene::Transparency; use rustc_span::{symbol::sym, symbol::Symbol, Span}; use std::num::NonZeroU32; +use version_check::Version; pub fn is_builtin_attr(attr: &Attribute) -> bool { attr.is_doc_comment() || attr.ident().filter(|ident| is_builtin_attr_name(ident.name)).is_some() @@ -568,11 +569,8 @@ pub fn find_crate_name(attrs: &[Attribute]) -> Option { /// Tests if a cfg-pattern matches the cfg set pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Features>) -> bool { - eval_condition(cfg, sess, &mut |cfg| { - let gate = find_gated_cfg(|sym| cfg.check_name(sym)); - if let (Some(feats), Some(gated_cfg)) = (features, gate) { - gate_cfg(&gated_cfg, cfg.span, sess, feats); - } + eval_condition(cfg, sess, features, &mut |cfg| { + try_gate_cfg(cfg, sess, features); let error = |span, msg| { sess.span_diagnostic.span_err(span, msg); true @@ -603,6 +601,13 @@ pub fn cfg_matches(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Feat }) } +fn try_gate_cfg(cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Features>) { + let gate = find_gated_cfg(|sym| cfg.check_name(sym)); + if let (Some(feats), Some(gated_cfg)) = (features, gate) { + gate_cfg(&gated_cfg, cfg.span, sess, feats); + } +} + fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &Features) { let (cfg, feature, has_feature) = gated_cfg; if !has_feature(features) && !cfg_span.allows_unstable(*feature) { @@ -616,9 +621,42 @@ fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &F pub fn eval_condition( cfg: &ast::MetaItem, sess: &ParseSess, + features: Option<&Features>, eval: &mut impl FnMut(&ast::MetaItem) -> bool, ) -> bool { match cfg.kind { + ast::MetaItemKind::List(ref mis) if cfg.name_or_empty() == sym::version => { + try_gate_cfg(cfg, sess, features); + let (min_version, span) = match &mis[..] { + [NestedMetaItem::Literal(Lit { kind: LitKind::Str(sym, ..), span, .. })] => { + (sym, span) + } + [NestedMetaItem::Literal(Lit { span, .. }) + | NestedMetaItem::MetaItem(MetaItem { span, .. })] => { + sess.span_diagnostic + .struct_span_err(*span, &*format!("expected string literal")) + .emit(); + return false; + } + [..] => { + sess.span_diagnostic + .struct_span_err(cfg.span, "expected single string literal") + .emit(); + return false; + } + }; + let min_version = match Version::parse(&min_version.as_str()) { + Some(ver) => ver, + None => { + sess.span_diagnostic.struct_span_err(*span, "invalid version string").emit(); + return false; + } + }; + let version = option_env!("CFG_VERSION").unwrap_or("unknown rustc version"); + let version = Version::parse(version).unwrap(); + + version >= min_version + } ast::MetaItemKind::List(ref mis) => { for mi in mis.iter() { if !mi.is_meta_item() { @@ -634,12 +672,12 @@ pub fn eval_condition( // The unwraps below may look dangerous, but we've already asserted // that they won't fail with the loop above. match cfg.name_or_empty() { - sym::any => { - mis.iter().any(|mi| eval_condition(mi.meta_item().unwrap(), sess, eval)) - } - sym::all => { - mis.iter().all(|mi| eval_condition(mi.meta_item().unwrap(), sess, eval)) - } + sym::any => mis + .iter() + .any(|mi| eval_condition(mi.meta_item().unwrap(), sess, features, eval)), + sym::all => mis + .iter() + .all(|mi| eval_condition(mi.meta_item().unwrap(), sess, features, eval)), sym::not => { if mis.len() != 1 { struct_span_err!( @@ -652,7 +690,7 @@ pub fn eval_condition( return false; } - !eval_condition(mis[0].meta_item().unwrap(), sess, eval) + !eval_condition(mis[0].meta_item().unwrap(), sess, features, eval) } _ => { struct_span_err!( diff --git a/src/librustc_attr/lib.rs b/src/librustc_attr/lib.rs index 9803501fb96..66c4495c5af 100644 --- a/src/librustc_attr/lib.rs +++ b/src/librustc_attr/lib.rs @@ -4,6 +4,8 @@ //! The goal is to move the definition of `MetaItem` and things that don't need to be in `syntax` //! to this crate. +#![feature(or_patterns)] + mod builtin; pub use builtin::*; diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index cec9e0ce3af..a1dd7a5ca52 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -562,6 +562,9 @@ declare_features! ( /// Allows the use of `#[target_feature]` on safe functions. (active, target_feature_11, "1.45.0", Some(69098), None), + /// Allow conditional compilation depending on rust version + (active, cfg_version, "1.45.0", Some(64796), None), + // ------------------------------------------------------------------------- // feature-group-end: actual feature gates // ------------------------------------------------------------------------- diff --git a/src/librustc_feature/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs index e4975aae6b9..466b318bca7 100644 --- a/src/librustc_feature/builtin_attrs.rs +++ b/src/librustc_feature/builtin_attrs.rs @@ -26,6 +26,7 @@ const GATED_CFGS: &[GatedCfg] = &[ (sym::target_has_atomic, sym::cfg_target_has_atomic, cfg_fn!(cfg_target_has_atomic)), (sym::target_has_atomic_load_store, sym::cfg_target_has_atomic, cfg_fn!(cfg_target_has_atomic)), (sym::sanitize, sym::cfg_sanitize, cfg_fn!(cfg_sanitize)), + (sym::version, sym::cfg_version, cfg_fn!(cfg_version)), ]; /// Find a gated cfg determined by the `pred`icate which is given the cfg's name. diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index 74d73404acd..f194506e660 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -192,6 +192,7 @@ symbols! { cfg_target_has_atomic, cfg_target_thread_local, cfg_target_vendor, + cfg_version, char, clippy, clone, @@ -805,6 +806,7 @@ symbols! { var, vec, Vec, + version, vis, visible_private_types, volatile, diff --git a/src/librustc_trait_selection/traits/on_unimplemented.rs b/src/librustc_trait_selection/traits/on_unimplemented.rs index cf29c4249c0..3fbc0b7f08e 100644 --- a/src/librustc_trait_selection/traits/on_unimplemented.rs +++ b/src/librustc_trait_selection/traits/on_unimplemented.rs @@ -81,7 +81,7 @@ impl<'tcx> OnUnimplementedDirective { None, ) })?; - attr::eval_condition(cond, &tcx.sess.parse_sess, &mut |_| true); + attr::eval_condition(cond, &tcx.sess.parse_sess, Some(tcx.features()), &mut |_| true); Some(cond.clone()) }; @@ -208,11 +208,16 @@ impl<'tcx> OnUnimplementedDirective { for command in self.subcommands.iter().chain(Some(self)).rev() { if let Some(ref condition) = command.condition { - if !attr::eval_condition(condition, &tcx.sess.parse_sess, &mut |c| { - c.ident().map_or(false, |ident| { - options.contains(&(ident.name, c.value_str().map(|s| s.to_string()))) - }) - }) { + if !attr::eval_condition( + condition, + &tcx.sess.parse_sess, + Some(tcx.features()), + &mut |c| { + c.ident().map_or(false, |ident| { + options.contains(&(ident.name, c.value_str().map(|s| s.to_string()))) + }) + }, + ) { debug!("evaluate: skipping {:?} due to condition", command); continue; } diff --git a/src/test/ui/feature-gates/feature-gate-cfg-version.rs b/src/test/ui/feature-gates/feature-gate-cfg-version.rs new file mode 100644 index 00000000000..71b82b7f749 --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-cfg-version.rs @@ -0,0 +1,28 @@ +#[cfg(version("1.44"))] +//~^ ERROR `cfg(version)` is experimental and subject to change +fn foo() -> bool { true } +#[cfg(not(version("1.44")))] +//~^ ERROR `cfg(version)` is experimental and subject to change +fn foo() -> bool { false } + +#[cfg(version("1.43", "1.44", "1.45"))] //~ ERROR: expected single string literal +//~^ ERROR `cfg(version)` is experimental and subject to change +fn bar() -> bool { false } +#[cfg(version(false))] //~ ERROR: expected string literal +//~^ ERROR `cfg(version)` is experimental and subject to change +fn bar() -> bool { false } +#[cfg(version("foo"))] //~ ERROR: invalid version string +//~^ ERROR `cfg(version)` is experimental and subject to change +fn bar() -> bool { false } +#[cfg(version("999"))] +//~^ ERROR `cfg(version)` is experimental and subject to change +fn bar() -> bool { false } +#[cfg(version("0"))] +//~^ ERROR `cfg(version)` is experimental and subject to change +fn bar() -> bool { true } + +fn main() { + assert!(foo()); + assert!(bar()); + assert!(cfg!(version("1.42"))); //~ ERROR `cfg(version)` is experimental and subject to change +} diff --git a/src/test/ui/feature-gates/feature-gate-cfg-version.stderr b/src/test/ui/feature-gates/feature-gate-cfg-version.stderr new file mode 100644 index 00000000000..48686dd4aeb --- /dev/null +++ b/src/test/ui/feature-gates/feature-gate-cfg-version.stderr @@ -0,0 +1,93 @@ +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:1:7 + | +LL | #[cfg(version("1.44"))] + | ^^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:4:11 + | +LL | #[cfg(not(version("1.44")))] + | ^^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:8:7 + | +LL | #[cfg(version("1.43", "1.44", "1.45"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error: expected single string literal + --> $DIR/feature-gate-cfg-version.rs:8:7 + | +LL | #[cfg(version("1.43", "1.44", "1.45"))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:11:7 + | +LL | #[cfg(version(false))] + | ^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error: expected string literal + --> $DIR/feature-gate-cfg-version.rs:11:15 + | +LL | #[cfg(version(false))] + | ^^^^^ + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:14:7 + | +LL | #[cfg(version("foo"))] + | ^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error: invalid version string + --> $DIR/feature-gate-cfg-version.rs:14:15 + | +LL | #[cfg(version("foo"))] + | ^^^^^ + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:17:7 + | +LL | #[cfg(version("999"))] + | ^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:20:7 + | +LL | #[cfg(version("0"))] + | ^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error[E0658]: `cfg(version)` is experimental and subject to change + --> $DIR/feature-gate-cfg-version.rs:27:18 + | +LL | assert!(cfg!(version("1.42"))); + | ^^^^^^^^^^^^^^^ + | + = note: see issue #64796 for more information + = help: add `#![feature(cfg_version)]` to the crate attributes to enable + +error: aborting due to 11 previous errors + +For more information about this error, try `rustc --explain E0658`.