use lazy_static::lazy_static;
use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro_error::{abort_call_site, emit_error};
use quote::quote_spanned;
use std::env;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
parse2,
spanned::Spanned,
token::Paren,
Attribute, Expr, Signature, Token,
};
use crate::precondition::CfgPrecondition;
pub(crate) use attr::Attr;
mod attr;
pub(crate) const HINT_REASON: &str = "<specify the reason why you can assure this here>";
lazy_static! {
pub(crate) static ref CRATE_NAME: String = {
match proc_macro_crate::crate_name("pre") {
Ok(name) => name,
Err(err) => match env::var("CARGO_PKG_NAME") {
Ok(val) if val == "pre" => "pre".into(),
_ => abort_call_site!("crate `pre` must be imported: {}", err),
},
}
};
}
pub(crate) enum AttributeAction {
Remove,
Keep,
}
pub(crate) fn visit_matching_attrs_parsed_mut<ParsedAttr: Parse + Spanned>(
attributes: &mut Vec<Attribute>,
attr_name: &str,
mut visit: impl FnMut(Attr<ParsedAttr>) -> AttributeAction,
) -> Option<Span> {
let mut span_of_all: Option<Span> = None;
attributes.retain(|attr| match Attr::from_inner(attr_name, attr) {
Some(attr) => {
let span = attr.span();
match visit(attr) {
AttributeAction::Remove => {
span_of_all = Some(match span_of_all.take() {
Some(old_span) => old_span.join(span).unwrap_or(span),
None => span,
});
false
}
AttributeAction::Keep => true,
}
}
None => true,
});
span_of_all
}
pub(crate) fn visit_matching_attrs_parsed<ParsedAttr: Parse + Spanned>(
attributes: &[Attribute],
attr_name: &str,
mut visit: impl FnMut(Attr<ParsedAttr>),
) {
for attr in attributes {
if let Some(attr) = Attr::from_inner(attr_name, attr) {
visit(attr);
}
}
}
pub(crate) fn attributes_of_expression(expr: &mut Expr) -> Option<&mut Vec<Attribute>> {
macro_rules! extract_attributes_from {
($expr:expr => $($variant:ident),*) => {
match $expr {
$(
Expr::$variant(e) => Some(&mut e.attrs),
)*
_ => None,
}
}
}
extract_attributes_from!(expr =>
Array, Assign, AssignOp, Async, Await, Binary, Block, Box, Break, Call, Cast,
Closure, Continue, Field, ForLoop, Group, If, Index, Let, Lit, Loop, Macro, Match,
MethodCall, Paren, Path, Range, Reference, Repeat, Return, Struct, Try, TryBlock, Tuple,
Type, Unary, Unsafe, While, Yield
)
}
pub(crate) fn add_span_to_signature(span: Span, signature: &mut Signature) {
signature.fn_token.span = signature.fn_token.span.join(span).unwrap_or(span);
if let Some(token) = &mut signature.constness {
token.span = token.span.join(span).unwrap_or(span);
}
if let Some(token) = &mut signature.asyncness {
token.span = token.span.join(span).unwrap_or(span);
}
if let Some(token) = &mut signature.unsafety {
token.span = token.span.join(span).unwrap_or(span);
}
if let Some(abi) = &mut signature.abi {
abi.extern_token.span = abi.extern_token.span.join(span).unwrap_or(span);
}
}
pub(crate) fn combine_cfg(preconditions: &[CfgPrecondition], _span: Span) -> Option<TokenStream> {
const MISMATCHED_CFG: &str = "mismatched `cfg` predicates for preconditions";
const MISMATCHED_CFG_NOTE: &str =
"all preconditions must have syntactically equal `cfg` predicates";
let render_cfg = |cfg: Option<&TokenStream>| cfg.map(|cfg| format!("{}", cfg));
let first_cfg = preconditions.first().and_then(|p| p.cfg.clone());
let first_cfg_rendered = render_cfg(first_cfg.as_ref());
for precondition in preconditions.iter().skip(1) {
if first_cfg_rendered != render_cfg(precondition.cfg.as_ref()) {
match (&first_cfg, &precondition.cfg) {
(Some(first_cfg), Some(current_cfg)) => {
emit_error!(
current_cfg.span(),
MISMATCHED_CFG;
note = MISMATCHED_CFG_NOTE;
note = first_cfg.span() => "`{}` != `{}`", first_cfg, current_cfg
);
}
(Some(cfg), None) | (None, Some(cfg)) => {
emit_error!(
cfg.span(),
MISMATCHED_CFG;
note = MISMATCHED_CFG_NOTE;
note = "some preconditions have a `cfg` predicate and some do not"
);
}
(None, None) => unreachable!("two `None`s are equal to each other"),
}
}
}
first_cfg
}
struct Parenthesized {
parentheses: Paren,
content: TokenStream,
}
impl Parse for Parenthesized {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let parentheses = parenthesized!(content in input);
Ok(Parenthesized {
parentheses,
content: content.parse()?,
})
}
}
fn parse_to_comma(input: &mut TokenStream) -> (TokenStream, Option<Token![,]>) {
let mut to_comma = TokenStream::new();
let mut comma = None;
let mut token_iter = input.clone().into_iter();
loop {
match token_iter.next() {
Some(TokenTree::Punct(p)) if p.as_char() == ',' => {
comma = Some(
parse2(TokenTree::from(p).into()).expect("`,` token tree is parsed as a comma"),
);
break;
}
Some(token_tree) => to_comma.extend(std::iter::once(token_tree)),
None => break,
}
}
*input = TokenStream::new();
input.extend(token_iter);
(to_comma, comma)
}
pub(crate) fn flatten_cfgs(attributes: &mut Vec<Attribute>) {
let mut i = 0;
while i < attributes.len() {
if attributes[i].path.is_ident("cfg_attr") {
let attribute = attributes.remove(i);
let (parentheses, mut input) = if let Ok(Parenthesized {
parentheses,
content: input,
}) = parse2(attribute.tokens.clone())
{
(parentheses, input)
} else {
attributes.insert(i, attribute);
i += 1;
continue;
};
let (cfg, comma) = parse_to_comma(&mut input);
if comma.is_none() {
attributes.insert(i, attribute);
i += 1;
continue;
}
loop {
let (attr_tokens, comma) = parse_to_comma(&mut input);
let new_attribute = Attribute {
pound_token: attribute.pound_token,
style: attribute.style,
bracket_token: attribute.bracket_token,
path: attribute.path.clone(),
tokens: quote_spanned! { parentheses.span=>
(#cfg, #attr_tokens)
},
};
attributes.insert(i, new_attribute);
i += 1;
if comma.is_none() {
break;
}
}
} else {
i += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_cfg_flattening() {
let mut transformed_func: syn::ItemFn = syn::parse_quote! {
#[cfg_attr(all(target_endian = "little", target_endian = "big"), attr1, attr2, attr3)]
#[cfg_attr(all(target_endian = "big", target_endian = "little"), attr4, attr5, attr6)]
fn foo() {}
};
flatten_cfgs(&mut transformed_func.attrs);
let desired_result: syn::ItemFn = syn::parse_quote! {
#[cfg_attr(all(target_endian = "little", target_endian = "big"), attr1)]
#[cfg_attr(all(target_endian = "little", target_endian = "big"), attr2)]
#[cfg_attr(all(target_endian = "little", target_endian = "big"), attr3)]
#[cfg_attr(all(target_endian = "big", target_endian = "little"), attr4)]
#[cfg_attr(all(target_endian = "big", target_endian = "little"), attr5)]
#[cfg_attr(all(target_endian = "big", target_endian = "little"), attr6)]
fn foo() {}
};
assert_eq!(transformed_func, desired_result);
}
}