Skip to content

Commit

Permalink
Support for version-based code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
taiki-e committed Aug 24, 2020
1 parent b81617a commit 9a416d2
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 157 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
# This is the minimum supported Rust version of this crate.
# When updating this, the reminder to update the minimum supported Rust version in README.md.
- 1.31.0
- 1.33.0
- 1.39.0
- stable
- beta
- nightly
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ members = ["test_suite"]
[lib]
proc-macro = true

[build-dependencies]
version_check = "0.9.2"

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
Expand Down
58 changes: 16 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,60 +29,34 @@ The current const_fn requires Rust 1.31 or later.

## Examples

When using like the following functions to control unstable features:

```toml
[features]
const_unstable = []
```

It can be written as follows:

```rust
#![cfg_attr(feature = "const_unstable", feature(const_fn))]
use const_fn::const_fn;

pub struct Foo<T> {
x:T,
// 1.36 and later compiler (including beta and nightly)
#[const_fn(1.36)]
pub const fn version() {
/* ... */
}

impl<T: Iterator> Foo<T> {
/// Constructs a new `Foo`.
#[const_fn(feature = "const_unstable")]
pub const fn new(x: T) -> Self {
Self { x }
}
// nightly compiler (including dev build)
#[const_fn(nightly)]
pub const fn nightly() {
/* ... */
}
```

Code like this will be generated:

```rust
#![cfg_attr(feature = "const_unstable", feature(const_fn))]

pub struct Foo<T> {
x:T,
// `cfg(...)`
#[const_fn(cfg(...))]
pub const fn cfg() {
/* ... */
}

impl<T: Iterator> Foo<T> {
/// Constructs a new `Foo`.
#[cfg(feature = "const_unstable")]
pub const fn new(x: T) -> Self {
Self { x }
}

/// Constructs a new `Foo`.
#[cfg(not(feature = "const_unstable"))]
pub fn new(x: T) -> Self {
Self { x }
}
// `cfg(feature = "...")`
#[const_fn(feature = "...")]
pub const fn feature() {
/* ... */
}
```

See [test_suite] for more examples.

[test_suite]: https://github.com/taiki-e/const_fn/tree/master/test_suite

## License

Licensed under either of
Expand Down
28 changes: 28 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::{env, fs, path::PathBuf};

fn main() {
let version = match Version::new() {
Some(version) => format!("{:#?}\n", version),
None => panic!("unexpected output from `rustc --version`"),
};

let out_dir = env::var_os("OUT_DIR").map(PathBuf::from).expect("OUT_DIR not set");
let out_file = out_dir.join("version.rs");
fs::write(out_file, version).expect("failed to write version.rs");
}

#[derive(Debug)]
struct Version {
minor: u16,
patch: u16,
nightly: bool,
}

impl Version {
fn new() -> Option<Self> {
let (version, channel, _date) = version_check::triple()?;
let (_major, minor, patch) = version.to_mmp();
let nightly = channel.is_nightly() || channel.is_dev();
Some(Version { minor, patch, nightly })
}
}
193 changes: 135 additions & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,33 @@
//!
//! # Examples
//!
//! When using like the following functions to control unstable features:
//!
//! ```toml
//! [features]
//! const_unstable = []
//! ```
//!
//! It can be written as follows:
//!
//! ```rust
//! #![cfg_attr(feature = "const_unstable", feature(const_fn))]
//! use const_fn::const_fn;
//!
//! pub struct Foo<T> {
//! x: T,
//! // 1.36 and later compiler (including beta and nightly)
//! #[const_fn(1.36)]
//! pub const fn version() {
//! /* ... */
//! }
//!
//! impl<T: Iterator> Foo<T> {
//! /// Constructs a new `Foo`.
//! #[const_fn(feature = "const_unstable")]
//! pub const fn new(x: T) -> Self {
//! Self { x }
//! }
//! // nightly compiler (including dev build)
//! #[const_fn(nightly)]
//! pub const fn nightly() {
//! /* ... */
//! }
//! # fn main() {}
//! ```
//!
//! Code like this will be generated:
//!
//! ```rust
//! #![cfg_attr(feature = "const_unstable", feature(const_fn))]
//!
//! pub struct Foo<T> {
//! x: T,
//! // `cfg(...)`
//! #[const_fn(cfg(...))]
//! pub const fn cfg() {
//! /* ... */
//! }
//!
//! impl<T: Iterator> Foo<T> {
//! /// Constructs a new `Foo`.
//! #[cfg(feature = "const_unstable")]
//! pub const fn new(x: T) -> Self {
//! Self { x }
//! }
//!
//! /// Constructs a new `Foo`.
//! #[cfg(not(feature = "const_unstable"))]
//! pub fn new(x: T) -> Self {
//! Self { x }
//! }
//! // `cfg(feature = "...")`
//! #[const_fn(feature = "...")]
//! pub const fn feature() {
//! /* ... */
//! }
//! # fn main() {}
//! ```
//!
//! See [test_suite] for more examples.
//!
//! [test_suite]: https://github.com/taiki-e/const_fn/tree/master/test_suite
#![doc(html_root_url = "https://docs.rs/const_fn/0.3.1")]
#![doc(test(
Expand All @@ -74,20 +46,20 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens};
use syn::Error;
use std::str::FromStr;
use syn::{
parse::{Parse, ParseStream},
parse_quote, Error, *,
};
use syn_mid::ItemFn;

/// An attribute for easy generation of a const function with conditional compilations.
/// See crate level documentation for details.
#[proc_macro_attribute]
pub fn const_fn(args: TokenStream, function: TokenStream) -> TokenStream {
let args = proc_macro2::TokenStream::from(args);

if args.is_empty() {
return Error::new_spanned(args, "`const_fn` requires an argument")
.to_compile_error()
.into();
}
let arg: Arg = syn::parse_macro_input!(args);

let mut item: ItemFn = syn::parse_macro_input!(function);

Expand All @@ -100,12 +72,117 @@ pub fn const_fn(args: TokenStream, function: TokenStream) -> TokenStream {
.into();
}

let mut token = quote!(#[cfg(#args)]);
token.extend(item.to_token_stream());
match arg {
Arg::Cfg(c) => {
let mut tokens = quote!(#[cfg(#c)]);
tokens.extend(item.to_token_stream());
item.attrs.push(parse_quote!(#[cfg(not(#c))]));
item.sig.constness = None;
tokens.extend(item.into_token_stream());
tokens.into()
}
Arg::Feature(f, e, s) => {
let mut tokens = quote!(#[cfg(#f #e #s)]);
tokens.extend(item.to_token_stream());
item.attrs.push(parse_quote!(#[cfg(not(#f #e #s))]));
item.sig.constness = None;
tokens.extend(item.into_token_stream());
tokens.into()
}
Arg::Version(req) => {
if req.major > 1 || req.minor > VERSION.minor {
item.sig.constness = None;
}
item.into_token_stream().into()
}
Arg::Nightly => {
if !VERSION.nightly {
item.sig.constness = None;
}
item.into_token_stream().into()
}
}
}

mod kw {
syn::custom_keyword!(nightly);
syn::custom_keyword!(feature);
syn::custom_keyword!(cfg);
}

enum Arg {
// `const_fn(feature = "...")`
Feature(kw::feature, Token![=], LitStr),
// `const_fn(cfg(...))`
Cfg(TokenStream2),
// `const_fn("1.36")`
Version(VersionReq),
// `const_fn(cfg(nightly))`
Nightly,
}

impl Parse for Arg {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::nightly) {
let _: kw::nightly = input.parse()?;
Ok(Arg::Nightly)
} else if lookahead.peek(kw::cfg) {
let _: kw::cfg = input.parse()?;
let content;
let _: token::Paren = syn::parenthesized!(content in input);
let t: TokenStream2 = content.parse()?;
Ok(Arg::Cfg(t))
} else if lookahead.peek(kw::feature) {
let f: kw::feature = input.parse()?;
let e: Token![=] = input.parse()?;
let s: LitStr = input.parse()?;
Ok(Arg::Feature(f, e, s))
} else if lookahead.peek(LitStr) {
let s: LitStr = input.parse()?;
match s.value().parse::<VersionReq>() {
Ok(req) => Ok(Arg::Version(req)),
Err(e) => Err(Error::new(s.span(), e)),
}
} else {
Err(lookahead.error())
}
}
}

struct VersionReq {
major: usize,
minor: usize,
}

item.attrs.push(syn::parse_quote!(#[cfg(not(#args))]));
item.sig.constness = None;
token.extend(item.into_token_stream());
impl FromStr for VersionReq {
type Err = String;

token.into()
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut pieces = s.split('.');
let major = pieces
.next()
.ok_or("need to specify the major version")?
.parse::<usize>()
.map_err(|e| e.to_string())?;
let minor = pieces
.next()
.ok_or("need to specify the minor version")?
.parse::<usize>()
.map_err(|e| e.to_string())?;
if let Some(s) = pieces.next() {
Err(format!("unexpected input: {}", s))
} else {
Ok(Self { major, minor })
}
}
}

#[derive(Debug)]
struct Version {
minor: usize,
patch: usize,
nightly: bool,
}

const VERSION: Version = include!(concat!(env!("OUT_DIR"), "/version.rs"));
6 changes: 3 additions & 3 deletions test_suite/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ fn main() {
};

if minor >= 31 || nightly {
println!("cargo:rustc-cfg=min_const_fn");
println!("cargo:rustc-cfg=has_min_const_fn");
}
if minor >= 33 || nightly {
println!("cargo:rustc-cfg=const_let");
println!("cargo:rustc-cfg=has_const_let");
}
if minor >= 39 || nightly {
println!("cargo:rustc-cfg=const_vec_new");
println!("cargo:rustc-cfg=has_const_vec_new");
}
if nightly {
println!("cargo:rustc-cfg=const_unstable");
Expand Down
Loading

0 comments on commit 9a416d2

Please sign in to comment.