use crate::target::Target;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned,
Error, Expr, LitStr, Result,
};
pub struct MatchTarget {
features: LitStr,
arms: Vec<(Target, Expr)>,
default_target: Option<Expr>,
}
impl Parse for MatchTarget {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let features = input.parse()?;
let mut arms = Vec::new();
let mut default_target = None;
while !input.is_empty() {
let arm: syn::Arm = input.parse()?;
if !arm.attrs.is_empty() {
return Err(Error::new(arm.attrs[0].span(), "unexpected attribute"));
}
let pat = arm.pat;
if let Some(guard) = arm.guard {
return Err(Error::new(guard.0.span(), "unexpected guard"));
}
if pat == parse_quote!(_) {
default_target = Some(*arm.body);
if !input.is_empty() {
return Err(Error::new(input.span(), "unreachable targets"));
}
} else {
arms.push((parse_quote!(#pat), *arm.body));
}
}
Ok(MatchTarget {
features,
arms,
default_target,
})
}
}
impl ToTokens for MatchTarget {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut exprs = Vec::new();
let mut not_targets = Vec::new();
for (target, expr) in &self.arms {
let arch = target.arch();
let features = target.features();
let selected_features = &self.features;
let cfg = crate::cfg::transform(
parse_quote! { #selected_features, all(target_arch = #arch #(, target_feature = #features)*) },
);
exprs.push(quote! {
#[cfg(all(#cfg, not(any(#(#not_targets),*))))]
{ #expr }
});
not_targets.push(cfg);
}
let default_expr = if let Some(expr) = &self.default_target {
quote! { #expr }
} else {
Error::new(Span::call_site(), "no matching target").to_compile_error()
};
quote! {
{
#(#exprs)*
#[cfg(not(any(#(#not_targets),*)))]
#default_expr
}
}
.to_tokens(tokens)
}
}