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 RFC 2523, #[cfg(version(..))] #71314

Merged
merged 4 commits into from
May 3, 2020
Merged
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
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);
mibac138 marked this conversation as resolved.
Show resolved Hide resolved
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();
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved

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 }
mibac138 marked this conversation as resolved.
Show resolved Hide resolved

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, do we need this? Or maybe we can try another crate semver?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this it's unlikely for anyone to run into this, but I wanted to explicitly document this problem. I have thought about using semver, however if I were to use semver::Version I'd need to make sure the version contains major, minor and patch versions, and if I were to use semver::VersionReq I'd need to check if the version literal doesn't contain things like >=1.0, <1.5, >1.2, <1.3 which the RFC only mentions as possible future work (However, as a first iteration, version(1.27.0) is simple and covers most use cases.). Unless it's okay to implement this using VersionReq and adding support for >, <, etc. syntax I think it's simpler to just use version_check despite this bug which I believe is unlikely to affect anyone.


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