use proc_macro2::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) without_token: bool,
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 mut with_token = None;
let mut without_token = false;
if input.peek(Ident) {
let kw: Ident = input.parse()?;
if kw == "with" {
with_token = Some(input.parse()?);
} else if kw == "without" {
let t: Ident = input.parse()?;
if t != "token" {
return Err(syn::Error::new_spanned(
t,
"expected `token` after `without` (the only `without` form is `without token`)",
));
}
without_token = true;
} else {
return Err(syn::Error::new_spanned(
kw,
"expected `with <token>` or `without token`",
));
}
}
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
};
if without_token && tiers.is_some() {
return Err(syn::Error::new(
proc_macro2::Span::call_site(),
"`without token` takes no tier list — it resolves to the caller's own tier",
));
}
Ok(IncantInput {
func_path,
args,
with_token,
without_token,
tiers,
})
}
}
pub(crate) fn incant_impl(input: IncantInput) -> TokenStream {
if input.without_token {
return syn::Error::new(
proc_macro2::Span::call_site(),
"`incant!(.. without token)` is only valid inside a tier-macro body \
(`#[arcane]`, `#[rite]`, `#[magetypes]`, `#[autoversion]`) — there is \
no caller tier to resolve the variant against in plain code",
)
.to_compile_error();
}
let func_path = &input.func_path;
let args = &input.args;
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 &input.tiers {
None => default_tiers(true),
Some((names, _)) => match resolve_tiers(names, error_span, true) {
Ok(t) => t,
Err(e) => return e.to_compile_error(),
},
};
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 = dispatch;
quote! {
{
#scalar_warning
#dispatch2
}
}
}
}
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]));
}
}
let call_args = build_call_args(args, "e! { __t });
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 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 = args
.iter()
.filter(|a| !crate::common::is_bare_ident_pub(a, "Token"));
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
}
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]));
}
}
let call_args = build_call_args(args, "e! { __t });
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 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 = args
.iter()
.filter(|a| !crate::common::is_bare_ident_pub(a, "Token"));
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: {
#[allow(unused_imports)]
use archmage::SimdToken;
#(#dispatch_arms)*
#fallback_call
}
};
expanded
}