use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
Ident, Token,
parse::{Parse, ParseStream},
};
use crate::common::*;
#[allow(unused_imports)]
use crate::common::{build_call_args, build_scalar_call_args};
use crate::tiers::*;
pub(crate) struct IncantInput {
pub(crate) func_path: syn::Path,
pub(crate) args: Vec<syn::Expr>,
pub(crate) with_token: Option<syn::Expr>,
pub(crate) tiers: Option<(Vec<String>, proc_macro2::Span)>,
}
impl Parse for IncantInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let func_path: syn::Path = input.parse()?;
let content;
syn::parenthesized!(content in input);
let args = content
.parse_terminated(syn::Expr::parse, Token![,])?
.into_iter()
.collect();
let with_token = if input.peek(Ident) {
let kw: Ident = input.parse()?;
if kw != "with" {
return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
}
Some(input.parse()?)
} else {
None
};
let tiers = if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
let bracket_content;
let bracket = syn::bracketed!(bracket_content in input);
let mut tier_names = Vec::new();
while !bracket_content.is_empty() {
tier_names.push(parse_one_tier(&bracket_content)?);
if bracket_content.peek(Token![,]) {
let _: Token![,] = bracket_content.parse()?;
}
}
Some((tier_names, bracket.span.join()))
} else {
None
};
Ok(IncantInput {
func_path,
args,
with_token,
tiers,
})
}
}
pub(crate) fn incant_impl(input: IncantInput) -> TokenStream {
let func_path = &input.func_path;
let args = &input.args;
let tier_names: Vec<String> = match &input.tiers {
Some((names, _)) => names.clone(),
None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
};
let last_segment_span = func_path
.segments
.last()
.map(|s| s.ident.span())
.unwrap_or_else(proc_macro2::Span::call_site);
let error_span = input
.tiers
.as_ref()
.map(|(_, span)| *span)
.unwrap_or(last_segment_span);
let scalar_warning = if let Some((names, _span)) = &input.tiers {
let is_additive = names
.iter()
.all(|n| n.starts_with('+') || n.starts_with('-'));
if !is_additive
&& !names.iter().any(|n| {
let base = n
.strip_prefix('+')
.unwrap_or(n)
.split('(')
.next()
.unwrap_or(n);
base == "scalar" || base == "default"
})
{
quote! {
#[deprecated(since = "0.9.9", note = "\
explicit incant! tier lists should include `scalar`. \
incant! always calls fn_scalar() as the final fallback. \
This will become a compile error in v1.0. \
Example: incant!(foo(x), [v3, neon, scalar])")]
#[allow(non_upper_case_globals)]
const __incant_missing_scalar_tier: () = ();
let _ = __incant_missing_scalar_tier;
}
} else {
quote! {}
}
} else {
quote! {}
};
let tiers = match resolve_tiers(&tier_names, error_span, true) {
Ok(t) => t,
Err(e) => return e.to_compile_error().into(),
};
let dispatch: TokenStream = if let Some(token_expr) = &input.with_token {
gen_incant_passthrough(func_path, args, token_expr, &tiers)
} else {
gen_incant_entry(func_path, args, &tiers)
};
if scalar_warning.is_empty() {
dispatch
} else {
let dispatch2: proc_macro2::TokenStream = dispatch.into();
quote! {
{
#scalar_warning
#dispatch2
}
}
.into()
}
}
pub(crate) fn gen_incant_passthrough(
func_path: &syn::Path,
args: &[syn::Expr],
token_expr: &syn::Expr,
tiers: &[ResolvedTier],
) -> TokenStream {
let mut dispatch_arms = Vec::new();
let mut arch_groups: Vec<(Option<&str>, Vec<&ResolvedTier>)> = Vec::new();
for rt in tiers {
if rt.name == "scalar" || rt.name == "default" {
continue; }
if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == rt.target_arch) {
group.1.push(rt);
} else {
arch_groups.push((rt.target_arch, vec![rt]));
}
}
for (target_arch, group_tiers) in &arch_groups {
let mut tier_checks = Vec::new();
for rt in group_tiers {
let fn_suffixed = suffix_path(func_path, rt.suffix);
let as_method = format_ident!("{}", rt.as_method);
let token_expr = quote! { __t };
let call_args = build_call_args(args, &token_expr);
let check = quote! {
if let Some(__t) = __incant_token.#as_method() {
break '__incant #fn_suffixed(#call_args);
}
};
if let Some(feat) = &rt.feature_gate {
let allow_attr = if rt.allow_unexpected_cfg {
quote! { #[allow(unexpected_cfgs)] }
} else {
quote! {}
};
tier_checks.push(quote! {
#allow_attr
#[cfg(feature = #feat)]
{ #check }
});
} else {
tier_checks.push(check);
}
}
let inner = quote! { #(#tier_checks)* };
if let Some(arch) = target_arch {
dispatch_arms.push(quote! {
#[cfg(target_arch = #arch)]
{ #inner }
});
} else {
dispatch_arms.push(inner);
}
}
let has_default = tiers.iter().any(|t| t.name == "default");
let fallback_arm = if has_default {
let fn_default = suffix_path(func_path, "default");
let default_args: Vec<syn::Expr> = args
.iter()
.filter(|a| !crate::common::is_bare_ident_pub(a, "Token"))
.cloned()
.collect();
quote! {
break '__incant #fn_default(#(#default_args),*);
}
} else if tiers.iter().any(|t| t.name == "scalar") {
let fn_scalar = suffix_path(func_path, "scalar");
let token_expr = quote! { __t };
let call_args = build_call_args(args, &token_expr);
quote! {
if let Some(__t) = __incant_token.as_scalar() {
break '__incant #fn_scalar(#call_args);
}
unreachable!("Token did not match any known variant")
}
} else {
quote! { unreachable!("Token did not match any known variant") }
};
let expanded = quote! {
'__incant: {
use archmage::IntoConcreteToken;
let __incant_token = #token_expr;
#(#dispatch_arms)*
#fallback_arm
}
};
expanded.into()
}
pub(crate) fn gen_incant_entry(
func_path: &syn::Path,
args: &[syn::Expr],
tiers: &[ResolvedTier],
) -> TokenStream {
let mut dispatch_arms = Vec::new();
let mut arch_groups: Vec<(Option<&str>, Vec<&ResolvedTier>)> = Vec::new();
for rt in tiers {
if rt.name == "scalar" || rt.name == "default" {
continue;
}
if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == rt.target_arch) {
group.1.push(rt);
} else {
arch_groups.push((rt.target_arch, vec![rt]));
}
}
for (target_arch, group_tiers) in &arch_groups {
let mut tier_checks = Vec::new();
for rt in group_tiers {
let fn_suffixed = suffix_path(func_path, rt.suffix);
let token_path: syn::Path = syn::parse_str(rt.token_path).unwrap();
let token_expr = quote! { __t };
let call_args = build_call_args(args, &token_expr);
let check = quote! {
if let Some(__t) = #token_path::summon() {
break '__incant #fn_suffixed(#call_args);
}
};
if let Some(feat) = &rt.feature_gate {
let allow_attr = if rt.allow_unexpected_cfg {
quote! { #[allow(unexpected_cfgs)] }
} else {
quote! {}
};
tier_checks.push(quote! {
#allow_attr
#[cfg(feature = #feat)]
{ #check }
});
} else {
tier_checks.push(check);
}
}
let inner = quote! { #(#tier_checks)* };
if let Some(arch) = target_arch {
dispatch_arms.push(quote! {
#[cfg(target_arch = #arch)]
{ #inner }
});
} else {
dispatch_arms.push(inner);
}
}
let has_default = tiers.iter().any(|rt| rt.name == "default");
let fallback_call = if has_default {
let fn_default = suffix_path(func_path, "default");
let default_args: Vec<syn::Expr> = args
.iter()
.filter(|a| !crate::common::is_bare_ident_pub(a, "Token"))
.cloned()
.collect();
quote! { #fn_default(#(#default_args),*) }
} else {
let fn_scalar = suffix_path(func_path, "scalar");
let scalar_args = build_scalar_call_args(args);
quote! { #fn_scalar(#scalar_args) }
};
let expanded = quote! {
'__incant: {
use archmage::SimdToken;
#(#dispatch_arms)*
#fallback_call
}
};
expanded.into()
}