use crate::target::Target; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned, Error, Expr, ExprLit, Lit, LitStr, Pat, Result, }; pub struct MatchTarget { features: LitStr, arms: Vec<(Target, Expr)>, default_target: Option, } impl Parse for MatchTarget { fn parse(input: ParseStream<'_>) -> Result { 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")); } fn parse_target(e: &ExprLit) -> Result { if let Lit::Str(s) = &e.lit { Target::parse(s) } else { Err(Error::new(e.span(), "expected a string literal")) } } match pat { Pat::Lit(lit) => { arms.push((parse_target(&lit)?, *arm.body)); } Pat::Or(or) => { for case in or.cases.iter() { if let Pat::Lit(lit) = case { arms.push((parse_target(lit)?, *arm.body.clone())); } else { return Err(Error::new(case.span(), "expected a string literal")); } } } Pat::Wild(_) => { default_target = Some(*arm.body); if !input.is_empty() { return Err(Error::new(input.span(), "unreachable targets")); } } _ => return Err(Error::new(pat.span(), "expected string literal")), } } 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! { target_features = #selected_features, all(target_arch = #arch #(, target_feature = #features)*) }, ).unwrap(); 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) } }