| //! An attribute for easy generation of const functions with conditional compilations. |
| //! |
| //! # Examples |
| //! |
| //! ```rust |
| //! use const_fn::const_fn; |
| //! |
| //! // function is `const` on specified version and later compiler (including beta and nightly) |
| //! #[const_fn("1.36")] |
| //! pub const fn version() { |
| //! /* ... */ |
| //! } |
| //! |
| //! // function is `const` on nightly compiler (including dev build) |
| //! #[const_fn(nightly)] |
| //! pub const fn nightly() { |
| //! /* ... */ |
| //! } |
| //! |
| //! // function is `const` if `cfg(...)` is true |
| //! # #[cfg(any())] |
| //! #[const_fn(cfg(...))] |
| //! # pub fn _cfg() { unimplemented!() } |
| //! pub const fn cfg() { |
| //! /* ... */ |
| //! } |
| //! |
| //! // function is `const` if `cfg(feature = "...")` is true |
| //! #[const_fn(feature = "...")] |
| //! pub const fn feature() { |
| //! /* ... */ |
| //! } |
| //! ``` |
| //! |
| //! # Alternatives |
| //! |
| //! This crate is proc-macro, but is very lightweight, and has no dependencies. |
| //! |
| //! You can manually define declarative macros with similar functionality (see [`if_rust_version`](https://github.com/ogoffart/if_rust_version#examples)), or [you can define the same function twice with different cfg](https://github.com/crossbeam-rs/crossbeam/blob/0b6ea5f69fde8768c1cfac0d3601e0b4325d7997/crossbeam-epoch/src/atomic.rs#L340-L372). |
| //! (Note: the former approach requires more macros to be defined depending on the number of version requirements, the latter approach requires more functions to be maintained manually) |
| |
| #![doc(html_root_url = "https://docs.rs/const_fn/0.4.3")] |
| #![doc(test( |
| no_crate_inject, |
| attr(deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code)) |
| ))] |
| #![forbid(unsafe_code)] |
| #![warn(future_incompatible, rust_2018_idioms, single_use_lifetimes, unreachable_pub)] |
| #![warn(clippy::all, clippy::default_trait_access)] |
| // mem::take, #[non_exhaustive], and Option::{as_deref, as_deref_mut} require Rust 1.40, |
| // matches! requires Rust 1.42, str::{strip_prefix, strip_suffix} requires Rust 1.45 |
| #![allow( |
| clippy::mem_replace_with_default, |
| clippy::manual_non_exhaustive, |
| clippy::option_as_ref_deref, |
| clippy::match_like_matches_macro, |
| clippy::manual_strip |
| )] |
| |
| // older compilers require explicit `extern crate`. |
| #[allow(unused_extern_crates)] |
| extern crate proc_macro; |
| |
| #[macro_use] |
| mod utils; |
| |
| mod ast; |
| mod error; |
| mod iter; |
| mod to_tokens; |
| |
| use proc_macro::{Delimiter, TokenStream, TokenTree}; |
| use std::str::FromStr; |
| |
| use crate::{ |
| ast::{Func, LitStr}, |
| error::Error, |
| to_tokens::ToTokens, |
| utils::{cfg_attrs, parse_as_empty, tt_span}, |
| }; |
| |
| type Result<T, E = Error> = std::result::Result<T, E>; |
| |
| /// An attribute for easy generation of const functions with conditional compilations. |
| /// See crate level documentation for details. |
| #[proc_macro_attribute] |
| pub fn const_fn(args: TokenStream, input: TokenStream) -> TokenStream { |
| let arg = match parse_arg(args) { |
| Ok(arg) => arg, |
| Err(e) => return e.to_compile_error(), |
| }; |
| let func = match ast::parse_input(input) { |
| Ok(func) => func, |
| Err(e) => return e.to_compile_error(), |
| }; |
| |
| expand(arg, func) |
| } |
| |
| fn expand(arg: Arg, mut func: Func) -> TokenStream { |
| match arg { |
| Arg::Cfg(cfg) => { |
| let (mut tokens, cfg_not) = cfg_attrs(cfg); |
| tokens.extend(func.to_token_stream()); |
| tokens.extend(cfg_not); |
| func.print_const = false; |
| tokens.extend(func.to_token_stream()); |
| tokens |
| } |
| Arg::Feature(feat) => { |
| let (mut tokens, cfg_not) = cfg_attrs(feat); |
| tokens.extend(func.to_token_stream()); |
| tokens.extend(cfg_not); |
| func.print_const = false; |
| tokens.extend(func.to_token_stream()); |
| tokens |
| } |
| Arg::Version(req) => { |
| if req.major > 1 || req.minor > VERSION.minor { |
| func.print_const = false; |
| } |
| func.to_token_stream() |
| } |
| Arg::Nightly => { |
| func.print_const = VERSION.nightly; |
| func.to_token_stream() |
| } |
| } |
| } |
| |
| enum Arg { |
| // `const_fn("...")` |
| Version(VersionReq), |
| // `const_fn(nightly)` |
| Nightly, |
| // `const_fn(cfg(...))` |
| Cfg(TokenStream), |
| // `const_fn(feature = "...")` |
| Feature(TokenStream), |
| } |
| |
| fn parse_arg(tokens: TokenStream) -> Result<Arg> { |
| let mut iter = tokens.into_iter(); |
| |
| let next = iter.next(); |
| let next_span = tt_span(next.as_ref()); |
| match next { |
| Some(TokenTree::Ident(i)) => match &*i.to_string() { |
| "nightly" => { |
| parse_as_empty(iter)?; |
| return Ok(Arg::Nightly); |
| } |
| "cfg" => { |
| return match iter.next().as_ref() { |
| Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => { |
| parse_as_empty(iter)?; |
| Ok(Arg::Cfg(g.stream())) |
| } |
| tt => Err(error!(tt_span(tt), "expected `(`")), |
| }; |
| } |
| "feature" => { |
| let next = iter.next(); |
| return match next.as_ref() { |
| Some(TokenTree::Punct(p)) if p.as_char() == '=' => match iter.next() { |
| Some(TokenTree::Literal(l)) => { |
| let l = LitStr::new(l)?; |
| parse_as_empty(iter)?; |
| Ok(Arg::Feature( |
| vec![TokenTree::Ident(i), next.unwrap(), l.token.into()] |
| .into_iter() |
| .collect(), |
| )) |
| } |
| tt => Err(error!(tt_span(tt.as_ref()), "expected string literal")), |
| }, |
| tt => Err(error!(tt_span(tt), "expected `=`")), |
| }; |
| } |
| _ => {} |
| }, |
| Some(TokenTree::Literal(l)) => { |
| if let Ok(l) = LitStr::new(l) { |
| parse_as_empty(iter)?; |
| return match l.value().parse::<VersionReq>() { |
| Ok(req) => Ok(Arg::Version(req)), |
| Err(e) => Err(error!(l.span(), "{}", e)), |
| }; |
| } |
| } |
| _ => {} |
| } |
| |
| Err(error!(next_span, "expected one of: `nightly`, `cfg`, `feature`, string literal")) |
| } |
| |
| struct VersionReq { |
| major: u32, |
| minor: u32, |
| } |
| |
| impl FromStr for VersionReq { |
| type Err = String; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| let mut pieces = s.split('.'); |
| let major = pieces |
| .next() |
| .ok_or("need to specify the major version")? |
| .parse::<u32>() |
| .map_err(|e| e.to_string())?; |
| let minor = pieces |
| .next() |
| .ok_or("need to specify the minor version")? |
| .parse::<u32>() |
| .map_err(|e| e.to_string())?; |
| if let Some(s) = pieces.next() { |
| Err(format!("unexpected input: .{}", s)) |
| } else { |
| Ok(Self { major, minor }) |
| } |
| } |
| } |
| |
| struct Version { |
| minor: u32, |
| nightly: bool, |
| } |
| |
| #[cfg(const_fn_has_build_script)] |
| const VERSION: Version = include!(concat!(env!("OUT_DIR"), "/version.rs")); |
| // If build script has not run or unable to determine version, it is considered as Rust 1.0. |
| #[cfg(not(const_fn_has_build_script))] |
| const VERSION: Version = Version { minor: 0, nightly: false }; |