Skip to content

Commit 014d7fb

Browse files
committed
WIP: expr: add support for declaring a minimum version
This allows crates to declare the minimum supported version in their code. Further use of `#[rustc]` attributes will then error if a tautology is discovered. Missing: - [ ] documentation updates - [ ] error impls/messages - [ ] test cases Fixes: dtolnay#4
1 parent c89e67f commit 014d7fb

File tree

3 files changed

+154
-20
lines changed

3 files changed

+154
-20
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ name = "rustc"
1414
proc-macro = true
1515

1616
[dependencies]
17+
lazy_static = "1"
1718
proc-macro2 = "0.4"
1819
quote = "0.6.8"
1920
syn = { version = "0.15.25", features = ["full"] }

src/expr.rs

Lines changed: 138 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use crate::bound::{Bound, Release};
22
use crate::date::Date;
3+
use crate::time;
34
use crate::version::{Channel, Version};
45
use syn::parse::{Parse, ParseStream, Result};
56
use syn::punctuated::Punctuated;
67
use syn::{parenthesized, token, Token};
8+
use std::sync::RwLock;
79

810
pub enum Expr {
911
Stable,
@@ -16,33 +18,142 @@ pub enum Expr {
1618
Not(Box<Expr>),
1719
Any(Vec<Expr>),
1820
All(Vec<Expr>),
21+
MinVer(Bound),
22+
}
23+
24+
pub enum ExprError {
25+
}
26+
27+
impl ExprError {
28+
fn nightly_date(mindate: Date, date: Date) -> Self {
29+
unimplemented!()
30+
}
31+
32+
fn nightly_channel(channel: Channel) -> Self {
33+
unimplemented!()
34+
}
35+
36+
fn nightly_release(release: Release) -> Self {
37+
unimplemented!()
38+
}
39+
40+
fn release(minrel: Release, release: Release) -> Self {
41+
unimplemented!()
42+
}
43+
44+
fn bad_bound(minver: Bound, bound: Bound) -> Self {
45+
unimplemented!()
46+
}
47+
48+
fn duplicate_minver(minver: Bound, new_minver: Bound) -> Self {
49+
unimplemented!()
50+
}
51+
}
52+
53+
pub type ExprResult<T> = std::result::Result<T, ExprError>;
54+
55+
lazy_static! {
56+
static ref MINVER: RwLock<Option<Bound>> = RwLock::new(None);
57+
}
58+
59+
fn check_minver_channel(minver: Option<Bound>, channel: Channel) -> ExprResult<()> {
60+
match minver {
61+
Some(Bound::Nightly(mindate)) => {
62+
match channel {
63+
Channel::Nightly(checkdate) => if checkdate < mindate {
64+
Err(ExprError::nightly_date(mindate, checkdate))
65+
} else {
66+
Ok(())
67+
},
68+
Channel::Stable | Channel::Beta => Err(ExprError::nightly_channel(channel)),
69+
_ => Ok(()),
70+
}
71+
},
72+
_ => Ok(()),
73+
}
74+
}
75+
76+
fn check_minver_bound(minver: Option<Bound>, bound: &Bound) -> ExprResult<()> {
77+
match minver {
78+
Some(minver) => if bound < &minver {
79+
Err(ExprError::bad_bound(minver, *bound))
80+
} else {
81+
Ok(())
82+
},
83+
None => Ok(()),
84+
}
85+
}
86+
87+
fn check_minver_release(minver: Option<Bound>, release: &Release) -> ExprResult<()> {
88+
match minver {
89+
Some(Bound::Nightly(_)) => Err(ExprError::nightly_release(*release)),
90+
Some(Bound::Stable(minrel)) => if release < &minrel {
91+
Err(ExprError::release(minrel, *release))
92+
} else {
93+
Ok(())
94+
},
95+
None => Ok(()),
96+
}
1997
}
2098

2199
impl Expr {
22-
pub fn eval(&self, rustc: Version) -> Result<bool> {
100+
pub fn eval(&self, rustc: Version) -> ExprResult<bool> {
23101
use self::Expr::*;
24102

103+
let minver = {
104+
let bound = MINVER.read().expect("minver lock poisoned");
105+
*bound
106+
};
107+
25108
Ok(match self {
26-
Stable => rustc.channel == Channel::Stable,
27-
Beta => rustc.channel == Channel::Beta,
28-
Nightly => match rustc.channel {
29-
Channel::Nightly(_) | Channel::Dev => true,
30-
Channel::Stable | Channel::Beta => false,
109+
Stable => {
110+
check_minver_channel(minver, Channel::Stable)?;
111+
rustc.channel == Channel::Stable
112+
},
113+
Beta => {
114+
check_minver_channel(minver, Channel::Beta)?;
115+
rustc.channel == Channel::Beta
116+
},
117+
Nightly => {
118+
check_minver_channel(minver, Channel::Nightly(time::today()))?;
119+
match rustc.channel {
120+
Channel::Nightly(_) | Channel::Dev => true,
121+
Channel::Stable | Channel::Beta => false,
122+
}
31123
},
32-
Date(date) => match rustc.channel {
33-
Channel::Nightly(rustc) => rustc == *date,
34-
Channel::Stable | Channel::Beta | Channel::Dev => false,
124+
Date(date) => {
125+
check_minver_channel(minver, Channel::Nightly(*date))?;
126+
match rustc.channel {
127+
Channel::Nightly(rustc) => rustc == *date,
128+
Channel::Stable | Channel::Beta | Channel::Dev => false,
129+
}
130+
},
131+
Since(bound) => {
132+
check_minver_bound(minver, bound)?;
133+
rustc >= *bound
134+
},
135+
Before(bound) => {
136+
check_minver_bound(minver, bound)?;
137+
rustc < *bound
35138
},
36-
Since(bound) => rustc >= *bound,
37-
Before(bound) => rustc < *bound,
38139
Release(release) => {
140+
check_minver_release(minver, release)?;
39141
rustc.channel == Channel::Stable
40142
&& rustc.minor == release.minor
41143
&& release.patch.map_or(true, |patch| rustc.patch == patch)
42144
}
43145
Not(expr) => !expr.eval(rustc)?,
44-
Any(exprs) => exprs.iter().map(|e| e.eval(rustc)).collect::<Result<Vec<_>>>()?.into_iter().any(|b| b),
45-
All(exprs) => exprs.iter().map(|e| e.eval(rustc)).collect::<Result<Vec<_>>>()?.into_iter().all(|b| b),
146+
Any(exprs) => exprs.iter().map(|e| e.eval(rustc)).collect::<ExprResult<Vec<_>>>()?.into_iter().any(|b| b),
147+
All(exprs) => exprs.iter().map(|e| e.eval(rustc)).collect::<ExprResult<Vec<_>>>()?.into_iter().all(|b| b),
148+
MinVer(bound) => {
149+
if let Some(minver) = minver {
150+
Err(ExprError::duplicate_minver(minver, *bound))?
151+
} else {
152+
let mut new_minver = MINVER.write().expect("minver lock poisoned");
153+
*new_minver = Some(*bound);
154+
true
155+
}
156+
},
46157
})
47158
}
48159
}
@@ -58,6 +169,7 @@ mod keyword {
58169
syn::custom_keyword!(not);
59170
syn::custom_keyword!(any);
60171
syn::custom_keyword!(all);
172+
syn::custom_keyword!(minver);
61173
}
62174

63175
impl Parse for Expr {
@@ -79,6 +191,8 @@ impl Parse for Expr {
79191
Self::parse_any(input)
80192
} else if lookahead.peek(keyword::all) {
81193
Self::parse_all(input)
194+
} else if lookahead.peek(keyword::minver) {
195+
Self::parse_minver(input)
82196
} else {
83197
Err(lookahead.error())
84198
}
@@ -174,4 +288,15 @@ impl Expr {
174288

175289
Ok(Expr::All(exprs.into_iter().collect()))
176290
}
291+
292+
fn parse_minver(input: ParseStream) -> Result<Self> {
293+
input.parse::<keyword::minver>()?;
294+
295+
let paren;
296+
parenthesized!(paren in input);
297+
let bound: Bound = paren.parse()?;
298+
paren.parse::<Option<Token![,]>>()?;
299+
300+
Ok(Expr::MinVer(bound))
301+
}
177302
}

src/lib.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
//!
138138
//! <br>
139139
140+
#[macro_use] extern crate lazy_static;
140141
extern crate proc_macro;
141142

142143
mod attr;
@@ -194,6 +195,11 @@ pub fn all(args: TokenStream, input: TokenStream) -> TokenStream {
194195
cfg("all", args, input)
195196
}
196197

198+
#[proc_macro_attribute]
199+
pub fn minver(args: TokenStream, input: TokenStream) -> TokenStream {
200+
cfg("minver", args, input)
201+
}
202+
197203
fn cfg(top: &str, args: TokenStream, input: TokenStream) -> TokenStream {
198204
match try_cfg(top, args, input) {
199205
Ok(tokens) => tokens,
@@ -213,10 +219,10 @@ fn try_cfg(top: &str, args: TokenStream, input: TokenStream) -> Result<TokenStre
213219
let expr: Expr = syn::parse2(full_args)?;
214220
let version = rustc::version()?;
215221

216-
if expr.eval(version)? {
217-
Ok(input)
218-
} else {
219-
Ok(TokenStream::new())
222+
match expr.eval(version) {
223+
Ok(true) => Ok(input),
224+
Ok(false) => Ok(TokenStream::new()),
225+
Err(err) => unimplemented!(),
220226
}
221227
}
222228

@@ -233,9 +239,11 @@ pub fn attr(args: TokenStream, input: TokenStream) -> TokenStream {
233239
fn try_attr(args: attr::Args, input: TokenStream) -> Result<TokenStream> {
234240
let version = rustc::version()?;
235241

236-
if !args.condition.eval(version)? {
237-
return Ok(input);
238-
}
242+
match args.condition.eval(version) {
243+
Ok(false) => return Ok(input),
244+
Err(err) => unimplemented!(),
245+
_ => (),
246+
};
239247

240248
match args.then {
241249
Then::Const(const_token) => {

0 commit comments

Comments
 (0)