Skip to content

Commit

Permalink
Rollup merge of #71314 - mibac138:cfg-version, r=petrochenkov
Browse files Browse the repository at this point in the history
Implement RFC 2523, `#[cfg(version(..))]`

Hi! This is my first contribution to rust, I hope I didn't miss anything. I tried to implement this feature so that `#[cfg(version(1.44.0))]` works but the parser was printing an error that I wasn't sure how to fix so I just opted for implementing `#[cfg(version("1.44.0"))]` (note the quotes).

Tracking issue: #64796
  • Loading branch information
Dylan-DPC authored May 3, 2020
2 parents 8cb8d9c + 8a77d1c commit ffe0a1c
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3593,6 +3593,7 @@ dependencies = [
"rustc_session",
"rustc_span",
"serialize",
"version_check",
]

[[package]]
Expand Down
34 changes: 34 additions & 0 deletions src/doc/unstable-book/src/language-features/cfg-version.md
Original file line number Diff line number Diff line change
@@ -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 {
// ...
}
}
```
2 changes: 2 additions & 0 deletions src/librustc_attr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ authors = ["The Rust Project Developers"]
name = "rustc_attr"
version = "0.0.0"
edition = "2018"
build = "build.rs"

[lib]
name = "rustc_attr"
Expand All @@ -19,3 +20,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"
4 changes: 4 additions & 0 deletions src/librustc_attr/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=CFG_VERSION");
}
63 changes: 50 additions & 13 deletions src/librustc_attr/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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()
Expand Down Expand Up @@ -568,11 +569,8 @@ pub fn find_crate_name(attrs: &[Attribute]) -> Option<Symbol> {

/// 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
Expand Down Expand Up @@ -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) {
Expand All @@ -616,9 +621,41 @@ 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 a version literal"))
.emit();
return false;
}
[..] => {
sess.span_diagnostic
.struct_span_err(cfg.span, "expected single version 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 literal").emit();
return false;
}
};
let version = Version::parse(env!("CFG_VERSION")).unwrap();

version >= min_version
}
ast::MetaItemKind::List(ref mis) => {
for mi in mis.iter() {
if !mi.is_meta_item() {
Expand All @@ -634,12 +671,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!(
Expand All @@ -652,7 +689,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!(
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_attr/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
3 changes: 3 additions & 0 deletions src/librustc_feature/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// -------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/librustc_feature/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_span/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ symbols! {
cfg_target_has_atomic,
cfg_target_thread_local,
cfg_target_vendor,
cfg_version,
char,
clippy,
clone,
Expand Down Expand Up @@ -805,6 +806,7 @@ symbols! {
var,
vec,
Vec,
version,
vis,
visible_private_types,
volatile,
Expand Down
17 changes: 11 additions & 6 deletions src/librustc_trait_selection/traits/on_unimplemented.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
};

Expand Down Expand Up @@ -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;
}
Expand Down
41 changes: 41 additions & 0 deletions src/test/ui/feature-gates/feature-gate-cfg-version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#[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 version literal
//~^ ERROR `cfg(version)` is experimental and subject to change
fn bar() -> bool { false }
#[cfg(version(false))] //~ ERROR: expected a version literal
//~^ ERROR `cfg(version)` is experimental and subject to change
fn bar() -> bool { false }
#[cfg(version("foo"))] //~ ERROR: invalid version literal
//~^ 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("-1"))] //~ ERROR: invalid version literal
//~^ ERROR `cfg(version)` is experimental and subject to change
fn bar() -> bool { false }
#[cfg(version("65536"))] //~ ERROR: invalid version literal
//~^ 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 }

#[cfg(version("1.65536.2"))]
//~^ ERROR `cfg(version)` is experimental and subject to change
fn version_check_bug() {}

fn main() {
// This should fail but due to a bug in version_check `1.65536.2` is interpreted as `1.2`.
// See https://github.com/SergioBenitez/version_check/issues/11
version_check_bug();
assert!(foo());
assert!(bar());
assert!(cfg!(version("1.42"))); //~ ERROR `cfg(version)` is experimental and subject to change
}
Loading

0 comments on commit ffe0a1c

Please sign in to comment.