use crate::dispatcher::{DispatchMethod, Dispatcher};
use crate::target::Target;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
parenthesized, parse::Parser, parse_quote, punctuated::Punctuated, spanned::Spanned, token,
Attribute, Error, ItemFn, LitStr, Meta, ReturnType, Type,
};
pub(crate) fn make_multiversioned_fn(
attr: TokenStream,
func: ItemFn,
) -> Result<TokenStream, syn::Error> {
if let ReturnType::Type(_, ty) = &func.sig.output {
if let Type::ImplTrait(_) = **ty {
return Err(Error::new(
ty.span(),
"cannot multiversion function with `impl Trait` return type",
));
}
}
let mut targets: Option<Vec<Target>> = None;
let mut inner_attrs: Option<Vec<Attribute>> = None;
let mut dispatcher: Option<DispatchMethod> = None;
let parser = syn::meta::parser(|meta| {
if targets.is_some() && (meta.path.is_ident("targets") || meta.path.is_ident("simd")) {
return Err(meta.error("can't specify `targets` or `simd` multiple times"));
}
if meta.path.is_ident("targets") {
if meta.input.peek(token::Paren) {
let content;
parenthesized!(content in meta.input);
targets = Some(
Punctuated::<Target, token::Comma>::parse_terminated(&content)?
.into_iter()
.collect(),
);
return Ok(());
} else {
let value = meta.value()?;
let s: LitStr = value.parse()?;
if s.value().as_str() == "simd" {
let default_targets = [
"x86_64+avx2+fma",
"x86_64+sse4.2",
"x86+avx2+fma",
"x86+sse4.2",
"x86+sse2",
"aarch64+neon",
];
targets = Some(
default_targets
.iter()
.map(|x| Target::parse(&LitStr::new(x, meta.path.span())).unwrap())
.collect(),
);
return Ok(());
}
return Err(meta.error("expected a list of features or \"simd\""));
}
}
if meta.path.is_ident("attrs") {
if inner_attrs.is_some() {
return Err(meta.error("can't specify `attrs` multiple times"));
}
inner_attrs = Some(Vec::new());
let content;
parenthesized!(content in meta.input);
inner_attrs = Some(
Punctuated::<Meta, token::Comma>::parse_terminated(&content)?
.into_iter()
.map(|meta| parse_quote! { #[#meta] })
.collect(),
);
return Ok(());
}
if meta.path.is_ident("dispatcher") {
if dispatcher.is_some() {
return Err(meta.error("can't specify `dispatcher` multiple times"));
}
let value = meta.value()?;
let s: LitStr = value.parse()?;
dispatcher = Some(match s.value().as_str() {
"default" => DispatchMethod::Default,
"static" => DispatchMethod::Static,
"direct" => DispatchMethod::Direct,
"indirect" => DispatchMethod::Indirect,
_ => {
return Err(meta.error("expected `default`, `static`, `direct`, or `indirect`"))
}
});
return Ok(());
};
Err(meta.error("unrecognized option"))
});
let span = attr.span();
parser.parse2(attr)?;
let targets = if let Some(targets) = targets {
for target in targets.iter() {
if !target.has_features_specified() {
return Err(Error::new(span, "target must have features specified"));
}
}
targets
} else {
return Err(Error::new(span, "expected `targets`"));
};
let inner_attrs = inner_attrs.unwrap_or_default();
let dispatcher = dispatcher.unwrap_or(DispatchMethod::Default);
Ok(Dispatcher {
targets,
func,
inner_attrs,
dispatcher,
}
.to_token_stream())
}