use proc_macro2::Ident;
use quote::{ToTokens, format_ident, quote};
use syn::{Attribute, GenericParam, Signature, Type, parse::ParseStream, token};
#[derive(Clone)]
pub(crate) struct LightFn {
pub attrs: Vec<Attribute>,
pub vis: syn::Visibility,
pub sig: Signature,
pub brace_token: token::Brace,
pub body: proc_macro2::TokenStream,
}
impl syn::parse::Parse for LightFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let vis: syn::Visibility = input.parse()?;
let sig: Signature = input.parse()?;
let content;
let brace_token = syn::braced!(content in input);
let body: proc_macro2::TokenStream = content.parse()?;
Ok(LightFn {
attrs,
vis,
sig,
brace_token,
body,
})
}
}
impl ToTokens for LightFn {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
for attr in &self.attrs {
attr.to_tokens(tokens);
}
self.vis.to_tokens(tokens);
self.sig.to_tokens(tokens);
self.brace_token.surround(tokens, |tokens| {
self.body.to_tokens(tokens);
});
}
}
pub(crate) fn filter_inline_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
attrs
.iter()
.filter(|attr| !attr.path().is_ident("inline"))
.collect()
}
pub(crate) fn is_lint_attr(attr: &Attribute) -> bool {
let path = attr.path();
path.is_ident("allow")
|| path.is_ident("expect")
|| path.is_ident("deny")
|| path.is_ident("warn")
|| path.is_ident("forbid")
}
pub(crate) fn body_attrs(attrs: &[Attribute], lint_levels: bool) -> Vec<Attribute> {
attrs
.iter()
.filter(|attr| (lint_levels && is_lint_attr(attr)) || attr.path().is_ident("track_caller"))
.cloned()
.collect()
}
pub(crate) fn expect_as_allow<'a>(
attrs: impl IntoIterator<Item = &'a Attribute>,
) -> Vec<Attribute> {
attrs
.into_iter()
.map(|attr| {
let mut attr = attr.clone();
if attr.path().is_ident("expect")
&& let syn::Meta::List(list) = &mut attr.meta
&& let Some(last) = list.path.segments.last_mut()
{
last.ident = syn::Ident::new("allow", last.ident.span());
}
attr
})
.collect()
}
pub(crate) fn gen_cfg_guard(
target_arch: Option<&str>,
cfg_feature: Option<&str>,
) -> proc_macro2::TokenStream {
match (target_arch, cfg_feature) {
(Some(arch), Some(feat)) => {
quote! { #[cfg(all(target_arch = #arch, feature = #feat))] }
}
(Some(arch), None) => quote! { #[cfg(target_arch = #arch)] },
(None, Some(feat)) => quote! { #[cfg(feature = #feat)] },
(None, None) => quote! {},
}
}
pub(crate) fn drop_attrs_equal_to(attrs: &mut Vec<Attribute>, guard: &proc_macro2::TokenStream) {
let wanted = guard.to_string();
if wanted.is_empty() {
return;
}
attrs.retain(|a| a.to_token_stream().to_string() != wanted);
}
pub(crate) fn build_turbofish(generics: &syn::Generics) -> proc_macro2::TokenStream {
let params: Vec<proc_macro2::TokenStream> = generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(tp) => {
let ident = &tp.ident;
Some(quote! { #ident })
}
GenericParam::Const(cp) => {
let ident = &cp.ident;
Some(quote! { #ident })
}
GenericParam::Lifetime(_) => None,
})
.collect();
if params.is_empty() {
quote! {}
} else {
quote! { ::<#(#params),*> }
}
}
pub(crate) fn tokens_contain_ident(tokens: &proc_macro2::TokenStream, names: &[&str]) -> bool {
tokens.clone().into_iter().any(|token| match token {
proc_macro2::TokenTree::Ident(id) => names.iter().any(|name| id == *name),
proc_macro2::TokenTree::Group(group) => tokens_contain_ident(&group.stream(), names),
_ => false,
})
}
pub(crate) fn replace_ident_in_tokens(
tokens: proc_macro2::TokenStream,
target: &str,
replacement: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let mut result = proc_macro2::TokenStream::new();
let mut tokens = tokens.into_iter().peekable();
while let Some(tt) = tokens.next() {
if target == "Token"
&& matches!(&tt, proc_macro2::TokenTree::Ident(id) if id == "incant" || id == "dispatch_variant")
&& matches!(tokens.peek(), Some(proc_macro2::TokenTree::Punct(p)) if p.as_char() == '!')
{
result.extend([tt, tokens.next().unwrap()]);
if let Some(proc_macro2::TokenTree::Group(group)) = tokens.peek() {
let group = group.clone();
tokens.next();
let parser =
|input: syn::parse::ParseStream| -> syn::Result<proc_macro2::TokenStream> {
let path: syn::Path = input.parse()?;
let args;
syn::parenthesized!(args in input);
let args = args.parse_terminated(syn::Expr::parse, syn::Token![,])?;
let rest: proc_macro2::TokenStream = input.parse()?;
let path =
replace_ident_in_tokens(path.to_token_stream(), target, replacement);
let args = args.iter().map(|arg| {
if matches!(arg, syn::Expr::Path(p) if p.path.is_ident("Token")) {
arg.to_token_stream()
} else {
replace_ident_in_tokens(arg.to_token_stream(), target, replacement)
}
});
let rest = replace_ident_in_tokens(rest, target, replacement);
Ok(quote! { #path(#(#args),*) #rest })
};
use syn::parse::{Parse, Parser};
let replaced = parser
.parse2(group.stream())
.unwrap_or_else(|_| group.stream());
let mut new_group = proc_macro2::Group::new(group.delimiter(), replaced);
new_group.set_span(group.span());
result.extend([proc_macro2::TokenTree::Group(new_group)]);
}
continue;
}
match tt {
proc_macro2::TokenTree::Ident(ref ident) if *ident == target => {
result.extend(replacement.clone());
}
proc_macro2::TokenTree::Group(group) => {
let stream = group.stream();
if !tokens_contain_ident(&stream, &[target]) {
result.extend(std::iter::once(proc_macro2::TokenTree::Group(group)));
continue;
}
let new_stream = replace_ident_in_tokens(stream, target, replacement);
let mut new_group = proc_macro2::Group::new(group.delimiter(), new_stream);
new_group.set_span(group.span());
result.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
}
other => {
result.extend(std::iter::once(other));
}
}
}
result
}
pub(crate) fn replace_self_in_tokens(
tokens: proc_macro2::TokenStream,
replacement: &Type,
) -> proc_macro2::TokenStream {
replace_ident_in_tokens(tokens, "Self", &replacement.to_token_stream())
}
pub(crate) fn generate_imports(
target_arch: Option<&str>,
magetypes_namespace: Option<&str>,
import_intrinsics: bool,
import_magetypes: bool,
) -> proc_macro2::TokenStream {
let mut imports = proc_macro2::TokenStream::new();
if import_intrinsics && let Some(arch) = target_arch {
let arch_ident = quote::format_ident!("{}", arch);
imports.extend(quote! {
#[allow(unused_imports)]
use archmage::intrinsics::#arch_ident::*;
});
}
if import_magetypes && let Some(ns) = magetypes_namespace {
let ns_ident = quote::format_ident!("{}", ns);
imports.extend(quote! {
#[allow(unused_imports)]
use magetypes::simd::#ns_ident::*;
#[allow(unused_imports)]
use magetypes::simd::backends::*;
});
}
imports
}
pub(crate) fn is_bare_ident(expr: &syn::Expr, name: &str) -> bool {
match expr {
syn::Expr::Path(p) => {
p.qself.is_none() && p.path.segments.len() == 1 && p.path.segments[0].ident == name
}
_ => false,
}
}
pub(crate) enum TokenPlacement {
Explicit,
Variable(usize),
None,
}
pub(crate) fn find_token_placement(
args: &[syn::Expr],
caller_token_ident: Option<&str>,
) -> TokenPlacement {
for arg in args.iter() {
if is_bare_ident(arg, "Token") {
return TokenPlacement::Explicit;
}
}
if let Some(ident_name) = caller_token_ident {
for (i, arg) in args.iter().enumerate() {
if is_bare_ident(arg, ident_name) {
return TokenPlacement::Variable(i);
}
}
}
TokenPlacement::None
}
pub(crate) fn build_call_args(
args: &[syn::Expr],
token_expr: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
build_call_args_with_ident(args, token_expr, None)
}
pub(crate) fn build_call_args_with_ident(
args: &[syn::Expr],
token_expr: &proc_macro2::TokenStream,
caller_token_ident: Option<&str>,
) -> proc_macro2::TokenStream {
match find_token_placement(args, caller_token_ident) {
TokenPlacement::Explicit => {
let replaced = args
.iter()
.map(|arg| replace_ident_in_tokens(arg.to_token_stream(), "Token", token_expr));
quote! { #(#replaced),* }
}
TokenPlacement::Variable(idx) => {
let replaced = args.iter().enumerate().map(|(i, arg)| {
if i == idx {
token_expr.clone()
} else {
arg.to_token_stream()
}
});
quote! { #(#replaced),* }
}
TokenPlacement::None => {
quote! { #token_expr, #(#args),* }
}
}
}
pub(crate) fn build_scalar_call_args(args: &[syn::Expr]) -> proc_macro2::TokenStream {
let scalar = quote! { archmage::ScalarToken };
build_call_args(args, &scalar)
}
pub(crate) fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
let mut suffixed = path.clone();
if let Some(last) = suffixed.segments.last_mut() {
last.ident = quote::format_ident!("{}_{}", last.ident, suffix);
}
suffixed
}
pub(crate) fn gen_tier_trait_assertion(
tier_traits: &[String],
token_ident: &Ident,
) -> proc_macro2::TokenStream {
if tier_traits.is_empty() {
return quote! {};
}
let bounds = tier_traits.iter().map(|name| {
let ident = format_ident!("{}", name);
quote! { + ::archmage::#ident }
});
quote! {
{
#[inline(always)]
const fn __archmage_assert_tier_trait<__T: ?Sized #(#bounds)*>(_: &__T) {}
__archmage_assert_tier_trait(&#token_ident);
}
}
}
#[cfg(test)]
mod dispatch_marker_tests {
use super::*;
#[test]
fn substitutes_types_but_preserves_dispatch_markers() {
let source = quote! {
fn f(t: Token) {
archmage::incant!(callee::<Token>(Token, Token::from_context(), wrap::<Token>()), [scalar]);
dispatch_variant!(other(value, Token) with t);
}
};
let expected = quote! {
fn f(t: archmage::ScalarToken) {
archmage::incant!(callee::<archmage::ScalarToken>(Token, archmage::ScalarToken::from_context(), wrap::<archmage::ScalarToken>()), [scalar]);
dispatch_variant!(other(value, Token) with t);
}
};
assert_eq!(
replace_ident_in_tokens(source, "Token", "e!(archmage::ScalarToken)).to_string(),
expected.to_string()
);
}
}
#[derive(Default)]
pub(crate) struct SharedOptions {
pub(crate) import_intrinsics: bool,
pub(crate) import_magetypes: bool,
pub(crate) cfg_feature: Option<String>,
}
pub(crate) fn parse_shared_option(
ident: &Ident,
input: syn::parse::ParseStream,
opts: &mut SharedOptions,
) -> syn::Result<bool> {
match ident.to_string().as_str() {
"import_intrinsics" => opts.import_intrinsics = true,
"import_magetypes" => opts.import_magetypes = true,
"cfg" => {
let content;
syn::parenthesized!(content in input);
let feat: Ident = content.parse()?;
opts.cfg_feature = Some(feat.to_string());
}
"stub" => {
return Err(syn::Error::new(
ident.span(),
"`stub` has been removed. Use `incant!` for cross-arch dispatch \
instead — it cfg-gates each architecture automatically.\n\
\n\
Before: #[arcane(stub)] fn process(token: X64V3Token, ...) { ... }\n\
After: #[arcane] fn process_v3(token: X64V3Token, ...) { ... }\n\
\x20 fn dispatch(...) { incant!(process(...)) }",
));
}
_ => return Ok(false),
}
Ok(true)
}
#[cfg(not(feature = "avx512"))]
pub(crate) fn avx512_import_error(
sig: &syn::Signature,
import_intrinsics: bool,
features: &[&str],
token_desc: &str,
) -> Option<proc_macro2::TokenStream> {
if !import_intrinsics || !features.iter().any(|f| f.starts_with("avx512")) {
return None;
}
let msg = format!(
"Using {token_desc} with `import_intrinsics` requires the `avx512` feature.\n\
\n\
Add to your Cargo.toml:\n\
\x20 archmage = {{ version = \"...\", features = [\"avx512\"] }}\n\
\n\
Without it, 512-bit safe memory ops (_mm512_loadu_ps etc.) are not available.\n\
If you only need value intrinsics (no memory ops), remove `import_intrinsics`."
);
Some(syn::Error::new_spanned(sig, msg).to_compile_error())
}
#[cfg(feature = "avx512")]
pub(crate) fn avx512_import_error(
_sig: &syn::Signature,
_import_intrinsics: bool,
_features: &[&str],
_token_desc: &str,
) -> Option<proc_macro2::TokenStream> {
None
}
pub(crate) fn missing_token_error(
sig: &syn::Signature,
macro_name: &str,
forms: &str,
) -> proc_macro2::TokenStream {
if let Some(trait_name) = crate::token_discovery::diagnose_featureless_token(sig) {
let msg = format!(
"`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
because it doesn't specify any CPU features.\n\
\n\
#[{macro_name}] needs concrete features to generate #[target_feature]. \
Use a concrete token or a feature trait:\n\
\n\
Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ...{}",
if macro_name == "rite" {
"\nTier names: #[rite(v3)], #[rite(neon)], #[rite(v4)], ..."
} else {
""
}
);
return syn::Error::new_spanned(sig, msg).to_compile_error();
}
let msg = format!("{macro_name} requires a token parameter{forms}");
syn::Error::new_spanned(sig, msg).to_compile_error()
}
pub(crate) fn rename_non_ident_params(sig: &mut syn::Signature) -> Vec<proc_macro2::TokenStream> {
let mut rebinds = Vec::new();
let mut counter = 0u32;
for arg in &mut sig.inputs {
let syn::FnArg::Typed(pat_type) = arg else {
continue;
};
if matches!(pat_type.pat.as_ref(), syn::Pat::Ident(_)) {
continue;
}
let generated = format_ident!("__archmage_arg_{}", counter);
counter += 1;
let original_pat = pat_type.pat.clone();
let ty = &pat_type.ty;
if !matches!(original_pat.as_ref(), syn::Pat::Wild(_)) {
rebinds.push(if matches!(ty.as_ref(), syn::Type::ImplTrait(_)) {
quote! { let #original_pat = #generated; }
} else {
quote! { let #original_pat: #ty = #generated; }
});
}
*pat_type.pat = syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: generated,
subpat: None,
});
}
rebinds
}
pub(crate) fn rename_wildcard_token(sig: &mut syn::Signature) -> Option<Ident> {
let template = sig.clone();
for arg in &mut sig.inputs {
let syn::FnArg::Typed(pat_type) = arg else {
continue;
};
if !matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
continue;
}
let probe = syn::Signature {
inputs: std::iter::once(syn::FnArg::Typed(pat_type.clone())).collect(),
..template.clone()
};
let Some(info) = crate::token_discovery::find_token_param(&probe) else {
continue;
};
*pat_type.pat = syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: info.ident.clone(),
subpat: None,
});
return Some(info.ident);
}
None
}
pub(crate) fn prepend_to_body(
body: &mut proc_macro2::TokenStream,
prefix: proc_macro2::TokenStream,
) {
if prefix.is_empty() {
return;
}
let original = std::mem::take(body);
*body = quote! { #prefix #original };
}
pub(crate) fn nested_self_param(receiver: &syn::Receiver, self_ty: &syn::Type) -> syn::FnArg {
let ty: proc_macro2::TokenStream = match &receiver.kind {
syn::ReceiverKind::Value => quote!(#self_ty),
syn::ReceiverKind::Reference(_, lifetime, mutability) => {
quote!(& #lifetime #mutability #self_ty)
}
syn::ReceiverKind::Typed(_, ty) => replace_self_in_tokens(ty.to_token_stream(), self_ty),
_ => quote!(#self_ty),
};
syn::parse_quote!(_self: #ty)
}
pub(crate) fn replace_self_value_in_tokens(
tokens: proc_macro2::TokenStream,
replacement: &Ident,
) -> proc_macro2::TokenStream {
let mut result = proc_macro2::TokenStream::new();
let mut tokens = tokens.into_iter().peekable();
let mut in_item_header = false;
while let Some(tt) = tokens.next() {
match tt {
proc_macro2::TokenTree::Ident(ref ident) if *ident == "impl" || *ident == "trait" => {
in_item_header = true;
result.extend([tt]);
}
proc_macro2::TokenTree::Ident(ref ident) if *ident == "self" => {
let is_path = matches!(tokens.peek(), Some(proc_macro2::TokenTree::Punct(p)) if p.as_char() == ':');
if is_path {
result.extend([tt]);
} else {
let mut replaced = replacement.clone();
replaced.set_span(ident.span());
result.extend([proc_macro2::TokenTree::Ident(replaced)]);
}
}
proc_macro2::TokenTree::Group(group)
if in_item_header && group.delimiter() == proc_macro2::Delimiter::Brace =>
{
in_item_header = false;
result.extend([proc_macro2::TokenTree::Group(group)]);
}
proc_macro2::TokenTree::Group(group) => {
let inner = replace_self_value_in_tokens(group.stream(), replacement);
let mut new_group = proc_macro2::Group::new(group.delimiter(), inner);
new_group.set_span(group.span());
result.extend([proc_macro2::TokenTree::Group(new_group)]);
}
other => result.extend([other]),
}
}
result
}
pub(crate) fn token_param_idents(sig: &syn::Signature) -> Vec<String> {
let mut out = Vec::new();
for arg in &sig.inputs {
let syn::FnArg::Typed(syn::PatType { pat, ty, .. }) = arg else {
continue;
};
let Some(info) = crate::token_discovery::extract_token_type_info(ty) else {
continue;
};
let is_token = match &info {
crate::token_discovery::TokenTypeInfo::Concrete(_) => true,
crate::token_discovery::TokenTypeInfo::ImplTrait(names) => {
crate::token_discovery::traits_to_features(names).is_some()
}
crate::token_discovery::TokenTypeInfo::Generic(name) => {
crate::token_discovery::find_generic_bounds(sig, name)
.and_then(|b| crate::token_discovery::traits_to_features(&b))
.is_some()
}
};
if is_token {
out.push(match pat.as_ref() {
syn::Pat::Ident(p) => p.ident.to_string(),
_ => "_".to_string(),
});
}
}
out
}
pub(crate) fn multiple_tokens_error(
sig: &syn::Signature,
macro_name: &str,
idents: &[String],
) -> proc_macro2::TokenStream {
let list = idents
.iter()
.map(|i| format!("`{i}`"))
.collect::<Vec<_>>()
.join(", ");
let hint = if macro_name == "rite" {
"name the tier instead: `#[rite(v3)]`, or keep one token parameter"
} else {
"keep the strongest token as the parameter and derive the weaker ones inside \
the body with its extractors (`token.v3()`, `token.v2()`), or split the \
function so each part takes one token"
};
let msg = format!(
"#[{macro_name}] found {} token parameters ({list}) and takes its CPU features \
from only one. {hint}.",
idents.len()
);
syn::Error::new_spanned(sig, msg).to_compile_error()
}