#![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)]
#![allow(
clippy::mem_replace_with_default,
clippy::manual_non_exhaustive,
clippy::option_as_ref_deref,
clippy::match_like_matches_macro,
clippy::manual_strip
)]
#[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>;
#[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 {
Version(VersionReq),
Nightly,
Cfg(TokenStream),
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"));
#[cfg(not(const_fn_has_build_script))]
const VERSION: Version = Version { minor: 0, nightly: false };