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

Support for version-based code generation #17

Merged
merged 1 commit into from
Aug 25, 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
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() {
/* ... */
Comment on lines +35 to +38
Copy link
Owner Author

Choose a reason for hiding this comment

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

It currently only accepts string literals as version requirements, like cfg_version does.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Filed #19

}

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() {
/* ... */
Comment on lines +41 to +44
Copy link
Owner Author

Choose a reason for hiding this comment

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

To use the nightly only unstable feature, it needs to enable the feature at crate-level, and in any case, it needs build-script. So, this may not be very useful in practice, but I'll keep it for now.

}
```

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 })
}
}
195 changes: 137 additions & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,35 @@
//!
//! # 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(...)`
//! # #[cfg(any())]
//! #[const_fn(cfg(...))]
//! # pub fn _cfg() { unimplemented!() }
//! 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 +48,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 +74,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("1.36")`
Version(VersionReq),
// `const_fn(nightly)`
Nightly,
// `const_fn(cfg(...))`
Cfg(TokenStream2),
// `const_fn(feature = "...")`
Feature(kw::feature, Token![=], LitStr),
}

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: u16,
minor: u16,
}

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::<u16>()
.map_err(|e| e.to_string())?;
let minor = pieces
.next()
.ok_or("need to specify the minor version")?
.parse::<u16>()
.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: u16,
patch: u16,
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