Skip to main content

archmage_macros/
lib.rs

1//! Proc-macros for archmage SIMD capability tokens.
2//!
3//! Provides `#[arcane]` attribute (with `#[arcane]` alias) to make raw intrinsics
4//! safe via token proof.
5
6use proc_macro::TokenStream;
7use quote::{ToTokens, format_ident, quote};
8use syn::{
9    Attribute, FnArg, GenericParam, Ident, PatType, Signature, Token, Type, TypeParamBound,
10    parse::{Parse, ParseStream},
11    parse_macro_input, parse_quote, token,
12};
13
14/// A function parsed with the body left as an opaque TokenStream.
15///
16/// Only the signature is fully parsed into an AST — the body tokens are collected
17/// without building any AST nodes (no expressions, statements, or patterns parsed).
18/// This saves ~2ms per function invocation at 100 lines of code.
19#[derive(Clone)]
20struct LightFn {
21    attrs: Vec<Attribute>,
22    vis: syn::Visibility,
23    sig: Signature,
24    brace_token: token::Brace,
25    body: proc_macro2::TokenStream,
26}
27
28impl Parse for LightFn {
29    fn parse(input: ParseStream) -> syn::Result<Self> {
30        let attrs = input.call(Attribute::parse_outer)?;
31        let vis: syn::Visibility = input.parse()?;
32        let sig: Signature = input.parse()?;
33        let content;
34        let brace_token = syn::braced!(content in input);
35        let body: proc_macro2::TokenStream = content.parse()?;
36        Ok(LightFn {
37            attrs,
38            vis,
39            sig,
40            brace_token,
41            body,
42        })
43    }
44}
45
46impl ToTokens for LightFn {
47    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
48        for attr in &self.attrs {
49            attr.to_tokens(tokens);
50        }
51        self.vis.to_tokens(tokens);
52        self.sig.to_tokens(tokens);
53        self.brace_token.surround(tokens, |tokens| {
54            self.body.to_tokens(tokens);
55        });
56    }
57}
58
59/// Filter out `#[inline]`, `#[inline(always)]`, `#[inline(never)]` from attributes.
60///
61/// Used to prevent duplicate inline attributes when the macro adds its own.
62/// Duplicate `#[inline]` is a warning that will become a hard error.
63fn filter_inline_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
64    attrs
65        .iter()
66        .filter(|attr| !attr.path().is_ident("inline"))
67        .collect()
68}
69
70/// Build a turbofish token stream from a function's generics.
71///
72/// Collects type and const generic parameters (skipping lifetimes) and returns
73/// a `::<A, B, N, M>` turbofish fragment. Returns empty tokens if there are no
74/// type/const generics to forward.
75///
76/// This is needed when the dispatcher or wrapper calls variant/sibling functions
77/// that have const generics not inferable from argument types alone.
78fn build_turbofish(generics: &syn::Generics) -> proc_macro2::TokenStream {
79    let params: Vec<proc_macro2::TokenStream> = generics
80        .params
81        .iter()
82        .filter_map(|param| match param {
83            GenericParam::Type(tp) => {
84                let ident = &tp.ident;
85                Some(quote! { #ident })
86            }
87            GenericParam::Const(cp) => {
88                let ident = &cp.ident;
89                Some(quote! { #ident })
90            }
91            GenericParam::Lifetime(_) => None,
92        })
93        .collect();
94    if params.is_empty() {
95        quote! {}
96    } else {
97        quote! { ::<#(#params),*> }
98    }
99}
100
101/// Replace all `Self` identifier tokens with a concrete type in a token stream.
102///
103/// Recurses into groups (braces, parens, brackets). Used for `#[arcane(_self = Type)]`
104/// to replace `Self` in both the return type and body without needing to parse the body.
105fn replace_self_in_tokens(
106    tokens: proc_macro2::TokenStream,
107    replacement: &Type,
108) -> proc_macro2::TokenStream {
109    let mut result = proc_macro2::TokenStream::new();
110    for tt in tokens {
111        match tt {
112            proc_macro2::TokenTree::Ident(ref ident) if ident == "Self" => {
113                result.extend(replacement.to_token_stream());
114            }
115            proc_macro2::TokenTree::Group(group) => {
116                let new_stream = replace_self_in_tokens(group.stream(), replacement);
117                let mut new_group = proc_macro2::Group::new(group.delimiter(), new_stream);
118                new_group.set_span(group.span());
119                result.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
120            }
121            other => {
122                result.extend(std::iter::once(other));
123            }
124        }
125    }
126    result
127}
128
129/// Arguments to the `#[arcane]` macro.
130#[derive(Default)]
131struct ArcaneArgs {
132    /// Use `#[inline(always)]` instead of `#[inline]` for the inner function.
133    /// Requires nightly Rust with `#![feature(target_feature_inline_always)]`.
134    inline_always: bool,
135    /// The concrete type to use for `self` receiver.
136    /// When specified, `self`/`&self`/`&mut self` is transformed to `_self: Type`/`&Type`/`&mut Type`.
137    /// Implies `nested = true`.
138    self_type: Option<Type>,
139    /// Generate an `unreachable!()` stub on the wrong architecture.
140    /// Default is false (cfg-out: no function emitted on wrong arch).
141    stub: bool,
142    /// Use nested inner function instead of sibling function.
143    /// Implied by `_self = Type`. Required for associated functions in impl blocks
144    /// that have no `self` receiver (the macro can't distinguish them from free functions).
145    nested: bool,
146    /// Inject `use archmage::intrinsics::{arch}::*;` (includes safe memory ops).
147    import_intrinsics: bool,
148    /// Inject `use magetypes::simd::{ns}::*;`, `use magetypes::simd::generic::*;`,
149    /// and `use magetypes::simd::backends::*;`.
150    import_magetypes: bool,
151}
152
153impl Parse for ArcaneArgs {
154    fn parse(input: ParseStream) -> syn::Result<Self> {
155        let mut args = ArcaneArgs::default();
156
157        while !input.is_empty() {
158            let ident: Ident = input.parse()?;
159            match ident.to_string().as_str() {
160                "inline_always" => args.inline_always = true,
161                "stub" => args.stub = true,
162                "nested" => args.nested = true,
163                "import_intrinsics" => args.import_intrinsics = true,
164                "import_magetypes" => args.import_magetypes = true,
165                "_self" => {
166                    let _: Token![=] = input.parse()?;
167                    args.self_type = Some(input.parse()?);
168                }
169                other => {
170                    return Err(syn::Error::new(
171                        ident.span(),
172                        format!("unknown arcane argument: `{}`", other),
173                    ));
174                }
175            }
176            // Consume optional comma
177            if input.peek(Token![,]) {
178                let _: Token![,] = input.parse()?;
179            }
180        }
181
182        // _self = Type implies nested (inner fn needed for Self replacement)
183        if args.self_type.is_some() {
184            args.nested = true;
185        }
186
187        Ok(args)
188    }
189}
190
191// Token-to-features and trait-to-features mappings are generated from
192// token-registry.toml by xtask. Regenerate with: cargo run -p xtask -- generate
193mod generated;
194use generated::{
195    canonical_token_to_tier_suffix, tier_to_canonical_token, token_to_arch, token_to_features,
196    token_to_magetypes_namespace, trait_to_arch, trait_to_features, trait_to_magetypes_namespace,
197};
198
199/// Result of extracting token info from a type.
200enum TokenTypeInfo {
201    /// Concrete token type (e.g., `Avx2Token`)
202    Concrete(String),
203    /// impl Trait with the trait names (e.g., `impl HasX64V2`)
204    ImplTrait(Vec<String>),
205    /// Generic type parameter name (e.g., `T`)
206    Generic(String),
207}
208
209/// Extract token type information from a type.
210fn extract_token_type_info(ty: &Type) -> Option<TokenTypeInfo> {
211    match ty {
212        Type::Path(type_path) => {
213            // Get the last segment of the path (e.g., "Avx2Token" from "archmage::Avx2Token")
214            type_path.path.segments.last().map(|seg| {
215                let name = seg.ident.to_string();
216                // Check if it's a known concrete token type
217                if token_to_features(&name).is_some() {
218                    TokenTypeInfo::Concrete(name)
219                } else {
220                    // Might be a generic type parameter like `T`
221                    TokenTypeInfo::Generic(name)
222                }
223            })
224        }
225        Type::Reference(type_ref) => {
226            // Handle &Token or &mut Token
227            extract_token_type_info(&type_ref.elem)
228        }
229        Type::ImplTrait(impl_trait) => {
230            // Handle `impl HasX64V2` or `impl HasX64V2 + HasNeon`
231            let traits: Vec<String> = extract_trait_names_from_bounds(&impl_trait.bounds);
232            if traits.is_empty() {
233                None
234            } else {
235                Some(TokenTypeInfo::ImplTrait(traits))
236            }
237        }
238        _ => None,
239    }
240}
241
242/// Extract trait names from type param bounds.
243fn extract_trait_names_from_bounds(
244    bounds: &syn::punctuated::Punctuated<TypeParamBound, Token![+]>,
245) -> Vec<String> {
246    bounds
247        .iter()
248        .filter_map(|bound| {
249            if let TypeParamBound::Trait(trait_bound) = bound {
250                trait_bound
251                    .path
252                    .segments
253                    .last()
254                    .map(|seg| seg.ident.to_string())
255            } else {
256                None
257            }
258        })
259        .collect()
260}
261
262/// Look up a generic type parameter in the function's generics.
263fn find_generic_bounds(sig: &Signature, type_name: &str) -> Option<Vec<String>> {
264    // Check inline bounds first (e.g., `fn foo<T: HasX64V2>(token: T)`)
265    for param in &sig.generics.params {
266        if let GenericParam::Type(type_param) = param
267            && type_param.ident == type_name
268        {
269            let traits = extract_trait_names_from_bounds(&type_param.bounds);
270            if !traits.is_empty() {
271                return Some(traits);
272            }
273        }
274    }
275
276    // Check where clause (e.g., `fn foo<T>(token: T) where T: HasX64V2`)
277    if let Some(where_clause) = &sig.generics.where_clause {
278        for predicate in &where_clause.predicates {
279            if let syn::WherePredicate::Type(pred_type) = predicate
280                && let Type::Path(type_path) = &pred_type.bounded_ty
281                && let Some(seg) = type_path.path.segments.last()
282                && seg.ident == type_name
283            {
284                let traits = extract_trait_names_from_bounds(&pred_type.bounds);
285                if !traits.is_empty() {
286                    return Some(traits);
287                }
288            }
289        }
290    }
291
292    None
293}
294
295/// Convert trait names to features, collecting all features from all traits.
296fn traits_to_features(trait_names: &[String]) -> Option<Vec<&'static str>> {
297    let mut all_features = Vec::new();
298
299    for trait_name in trait_names {
300        if let Some(features) = trait_to_features(trait_name) {
301            for &feature in features {
302                if !all_features.contains(&feature) {
303                    all_features.push(feature);
304                }
305            }
306        }
307    }
308
309    if all_features.is_empty() {
310        None
311    } else {
312        Some(all_features)
313    }
314}
315
316/// Trait names that don't map to any CPU features. These are valid in the type
317/// system but cannot be used as token bounds in `#[arcane]`/`#[rite]` because
318/// the macros need concrete features to generate `#[target_feature]` attributes.
319const FEATURELESS_TRAIT_NAMES: &[&str] = &["SimdToken", "IntoConcreteToken"];
320
321/// Check if any trait names are featureless (no CPU feature mapping).
322/// Returns the first featureless trait name found.
323fn find_featureless_trait(trait_names: &[String]) -> Option<&'static str> {
324    for name in trait_names {
325        for &featureless in FEATURELESS_TRAIT_NAMES {
326            if name == featureless {
327                return Some(featureless);
328            }
329        }
330    }
331    None
332}
333
334/// Diagnose why `find_token_param` failed. Returns the name of a featureless
335/// trait if the signature has a parameter bounded by one (e.g., `SimdToken`).
336fn diagnose_featureless_token(sig: &Signature) -> Option<&'static str> {
337    for arg in &sig.inputs {
338        if let FnArg::Typed(PatType { ty, .. }) = arg
339            && let Some(info) = extract_token_type_info(ty)
340        {
341            match &info {
342                TokenTypeInfo::ImplTrait(names) => {
343                    if let Some(name) = find_featureless_trait(names) {
344                        return Some(name);
345                    }
346                }
347                TokenTypeInfo::Generic(type_name) => {
348                    // Check if the type name itself is a featureless trait
349                    // (e.g., `token: SimdToken` used as a bare path)
350                    let as_vec = vec![type_name.clone()];
351                    if let Some(name) = find_featureless_trait(&as_vec) {
352                        return Some(name);
353                    }
354                    // Check generic bounds (e.g., `T: SimdToken`)
355                    if let Some(bounds) = find_generic_bounds(sig, type_name)
356                        && let Some(name) = find_featureless_trait(&bounds)
357                    {
358                        return Some(name);
359                    }
360                }
361                TokenTypeInfo::Concrete(_) => {}
362            }
363        }
364    }
365    None
366}
367
368/// Result of finding a token parameter in a function signature.
369struct TokenParamInfo {
370    /// The parameter identifier (e.g., `token`)
371    ident: Ident,
372    /// Target features to enable (e.g., `["avx2", "fma"]`)
373    features: Vec<&'static str>,
374    /// Target architecture (Some for concrete tokens, None for traits/generics)
375    target_arch: Option<&'static str>,
376    /// Concrete token type name (Some for concrete tokens, None for traits/generics)
377    token_type_name: Option<String>,
378    /// Magetypes width namespace (e.g., "v3", "neon", "wasm128")
379    magetypes_namespace: Option<&'static str>,
380}
381
382/// Resolve magetypes namespace from a list of trait names.
383/// Returns the first matching namespace found.
384fn traits_to_magetypes_namespace(trait_names: &[String]) -> Option<&'static str> {
385    for name in trait_names {
386        if let Some(ns) = trait_to_magetypes_namespace(name) {
387            return Some(ns);
388        }
389    }
390    None
391}
392
393/// Given trait bound names, return the first matching target architecture.
394fn traits_to_arch(trait_names: &[String]) -> Option<&'static str> {
395    for name in trait_names {
396        if let Some(arch) = trait_to_arch(name) {
397            return Some(arch);
398        }
399    }
400    None
401}
402
403/// Find the first token parameter in a function signature.
404fn find_token_param(sig: &Signature) -> Option<TokenParamInfo> {
405    for arg in &sig.inputs {
406        match arg {
407            FnArg::Receiver(_) => {
408                // Self receivers (self, &self, &mut self) are not yet supported.
409                // The macro creates an inner function, and Rust's inner functions
410                // cannot have `self` parameters. Supporting this would require
411                // AST rewriting to replace `self` with a regular parameter.
412                // See the module docs for the workaround.
413                continue;
414            }
415            FnArg::Typed(PatType { pat, ty, .. }) => {
416                if let Some(info) = extract_token_type_info(ty) {
417                    let (features, arch, token_name, mage_ns) = match info {
418                        TokenTypeInfo::Concrete(ref name) => {
419                            let features = token_to_features(name).map(|f| f.to_vec());
420                            let arch = token_to_arch(name);
421                            let ns = token_to_magetypes_namespace(name);
422                            (features, arch, Some(name.clone()), ns)
423                        }
424                        TokenTypeInfo::ImplTrait(ref trait_names) => {
425                            let ns = traits_to_magetypes_namespace(trait_names);
426                            let arch = traits_to_arch(trait_names);
427                            (traits_to_features(trait_names), arch, None, ns)
428                        }
429                        TokenTypeInfo::Generic(type_name) => {
430                            // Look up the generic parameter's bounds
431                            let bounds = find_generic_bounds(sig, &type_name);
432                            let features = bounds.as_ref().and_then(|t| traits_to_features(t));
433                            let ns = bounds
434                                .as_ref()
435                                .and_then(|t| traits_to_magetypes_namespace(t));
436                            let arch = bounds.as_ref().and_then(|t| traits_to_arch(t));
437                            (features, arch, None, ns)
438                        }
439                    };
440
441                    if let Some(features) = features {
442                        // Extract parameter name (or synthesize one for wildcard `_`)
443                        let ident = match pat.as_ref() {
444                            syn::Pat::Ident(pat_ident) => Some(pat_ident.ident.clone()),
445                            syn::Pat::Wild(w) => {
446                                Some(Ident::new("__archmage_token", w.underscore_token.span))
447                            }
448                            _ => None,
449                        };
450                        if let Some(ident) = ident {
451                            return Some(TokenParamInfo {
452                                ident,
453                                features,
454                                target_arch: arch,
455                                token_type_name: token_name,
456                                magetypes_namespace: mage_ns,
457                            });
458                        }
459                    }
460                }
461            }
462        }
463    }
464    None
465}
466
467/// Represents the kind of self receiver and the transformed parameter.
468enum SelfReceiver {
469    /// `self` (by value/move)
470    Owned,
471    /// `&self` (shared reference)
472    Ref,
473    /// `&mut self` (mutable reference)
474    RefMut,
475}
476
477/// Generate import statements to prepend to a function body.
478///
479/// Returns a `TokenStream` of `use` statements based on the import flags,
480/// target architecture, and magetypes namespace.
481fn generate_imports(
482    target_arch: Option<&str>,
483    magetypes_namespace: Option<&str>,
484    import_intrinsics: bool,
485    import_magetypes: bool,
486) -> proc_macro2::TokenStream {
487    let mut imports = proc_macro2::TokenStream::new();
488
489    if import_intrinsics && let Some(arch) = target_arch {
490        let arch_ident = format_ident!("{}", arch);
491        imports.extend(quote! {
492            #[allow(unused_imports)]
493            use archmage::intrinsics::#arch_ident::*;
494        });
495        // ScalarToken or unknown arch: import_intrinsics is a no-op
496    }
497
498    if import_magetypes && let Some(ns) = magetypes_namespace {
499        let ns_ident = format_ident!("{}", ns);
500        imports.extend(quote! {
501            #[allow(unused_imports)]
502            use magetypes::simd::#ns_ident::*;
503            #[allow(unused_imports)]
504            use magetypes::simd::backends::*;
505        });
506    }
507
508    imports
509}
510
511/// Shared implementation for arcane/arcane macros.
512fn arcane_impl(mut input_fn: LightFn, macro_name: &str, args: ArcaneArgs) -> TokenStream {
513    // Check for self receiver
514    let has_self_receiver = input_fn
515        .sig
516        .inputs
517        .first()
518        .map(|arg| matches!(arg, FnArg::Receiver(_)))
519        .unwrap_or(false);
520
521    // Nested mode is required when _self = Type is used (for Self replacement in nested fn).
522    // In sibling mode, self/Self work naturally since both fns live in the same impl scope.
523    // However, if there's a self receiver in nested mode, we still need _self = Type.
524    if has_self_receiver && args.nested && args.self_type.is_none() {
525        let msg = format!(
526            "{} with self receiver in nested mode requires `_self = Type` argument.\n\
527             Example: #[{}(nested, _self = MyType)]\n\
528             Use `_self` (not `self`) in the function body to refer to self.\n\
529             \n\
530             Alternatively, remove `nested` to use sibling expansion (default), \
531             which handles self/Self naturally.",
532            macro_name, macro_name
533        );
534        return syn::Error::new_spanned(&input_fn.sig, msg)
535            .to_compile_error()
536            .into();
537    }
538
539    // Find the token parameter, its features, target arch, and token type name
540    let TokenParamInfo {
541        ident: _token_ident,
542        features,
543        target_arch,
544        token_type_name,
545        magetypes_namespace,
546    } = match find_token_param(&input_fn.sig) {
547        Some(result) => result,
548        None => {
549            // Check for specific misuse: featureless traits like SimdToken
550            if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
551                let msg = format!(
552                    "`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
553                     because it doesn't specify any CPU features.\n\
554                     \n\
555                     #[{macro_name}] needs concrete features to generate #[target_feature]. \
556                     Use a concrete token or a feature trait:\n\
557                     \n\
558                     Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
559                     Feature traits:  impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
560                );
561                return syn::Error::new_spanned(&input_fn.sig, msg)
562                    .to_compile_error()
563                    .into();
564            }
565            let msg = format!(
566                "{} requires a token parameter. Supported forms:\n\
567                 - Concrete: `token: X64V3Token`\n\
568                 - impl Trait: `token: impl HasX64V2`\n\
569                 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`\n\
570                 - With self: `#[{}(_self = Type)] fn method(&self, token: impl HasNeon, ...)`",
571                macro_name, macro_name
572            );
573            return syn::Error::new_spanned(&input_fn.sig, msg)
574                .to_compile_error()
575                .into();
576        }
577    };
578
579    // Prepend import statements to body if requested
580    let body_imports = generate_imports(
581        target_arch,
582        magetypes_namespace,
583        args.import_intrinsics,
584        args.import_magetypes,
585    );
586    if !body_imports.is_empty() {
587        let original_body = &input_fn.body;
588        input_fn.body = quote! {
589            #body_imports
590            #original_body
591        };
592    }
593
594    // Build target_feature attributes
595    let target_feature_attrs: Vec<Attribute> = features
596        .iter()
597        .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
598        .collect();
599
600    // Rename wildcard patterns (`_: Type`) to named params so the inner/sibling call works
601    let mut wild_rename_counter = 0u32;
602    for arg in &mut input_fn.sig.inputs {
603        if let FnArg::Typed(pat_type) = arg
604            && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
605        {
606            let ident = format_ident!("__archmage_wild_{}", wild_rename_counter);
607            wild_rename_counter += 1;
608            *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
609                attrs: vec![],
610                by_ref: None,
611                mutability: None,
612                ident,
613                subpat: None,
614            });
615        }
616    }
617
618    // Choose inline attribute based on args
619    let inline_attr: Attribute = if args.inline_always {
620        parse_quote!(#[inline(always)])
621    } else {
622        parse_quote!(#[inline])
623    };
624
625    // On wasm32, #[target_feature(enable = "simd128")] functions are safe (Rust 1.54+).
626    // The wasm validation model guarantees unsupported instructions trap deterministically,
627    // so there's no UB from feature mismatch. Skip the unsafe wrapper entirely.
628    if target_arch == Some("wasm32") {
629        return arcane_impl_wasm_safe(
630            input_fn,
631            &args,
632            token_type_name,
633            target_feature_attrs,
634            inline_attr,
635        );
636    }
637
638    if args.nested {
639        arcane_impl_nested(
640            input_fn,
641            &args,
642            target_arch,
643            token_type_name,
644            target_feature_attrs,
645            inline_attr,
646        )
647    } else {
648        arcane_impl_sibling(
649            input_fn,
650            &args,
651            target_arch,
652            token_type_name,
653            target_feature_attrs,
654            inline_attr,
655        )
656    }
657}
658
659/// WASM-safe expansion: emits rite-style output (no unsafe wrapper).
660///
661/// On wasm32, `#[target_feature(enable = "simd128")]` is safe — the wasm validation
662/// model traps deterministically on unsupported instructions, so there's no UB.
663/// We emit the function directly with `#[target_feature]` + `#[inline]`, like `#[rite]`.
664///
665/// If `_self = Type` is set, we inject `let _self = self;` at the top of the body
666/// (the function stays in impl scope, so `Self` resolves naturally — no replacement needed).
667fn arcane_impl_wasm_safe(
668    input_fn: LightFn,
669    args: &ArcaneArgs,
670    token_type_name: Option<String>,
671    target_feature_attrs: Vec<Attribute>,
672    inline_attr: Attribute,
673) -> TokenStream {
674    let vis = &input_fn.vis;
675    let sig = &input_fn.sig;
676    let fn_name = &sig.ident;
677    let attrs = &input_fn.attrs;
678
679    let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
680
681    // If _self = Type is set, inject `let _self = self;` at top of body so user code
682    // referencing `_self` works. The function remains in impl scope, so `Self` resolves
683    // naturally — no Self replacement needed (unlike nested mode's inner fn).
684    let body = if args.self_type.is_some() {
685        let original_body = &input_fn.body;
686        quote! {
687            let _self = self;
688            #original_body
689        }
690    } else {
691        input_fn.body.clone()
692    };
693
694    // Prepend target_feature + inline attrs, filtering user #[inline] to avoid duplicates
695    let mut new_attrs = target_feature_attrs;
696    new_attrs.push(inline_attr);
697    for attr in filter_inline_attrs(attrs) {
698        new_attrs.push(attr.clone());
699    }
700
701    let stub = if args.stub {
702        // Build stub args for suppressing unused-variable warnings
703        let stub_args: Vec<proc_macro2::TokenStream> = sig
704            .inputs
705            .iter()
706            .filter_map(|arg| match arg {
707                FnArg::Typed(pat_type) => {
708                    if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
709                        let ident = &pat_ident.ident;
710                        Some(quote!(#ident))
711                    } else {
712                        None
713                    }
714                }
715                FnArg::Receiver(_) => None,
716            })
717            .collect();
718
719        quote! {
720            #[cfg(not(target_arch = "wasm32"))]
721            #vis #sig {
722                let _ = (#(#stub_args),*);
723                unreachable!(
724                    "BUG: {}() was called but requires {} (target_arch = \"wasm32\"). \
725                     {}::summon() returns None on this architecture, so this function \
726                     is unreachable in safe code. If you used forge_token_dangerously(), \
727                     that is the bug.",
728                    stringify!(#fn_name),
729                    #token_type_str,
730                    #token_type_str,
731                )
732            }
733        }
734    } else {
735        quote! {}
736    };
737
738    let expanded = quote! {
739        #[cfg(target_arch = "wasm32")]
740        #(#new_attrs)*
741        #vis #sig {
742            #body
743        }
744
745        #stub
746    };
747
748    expanded.into()
749}
750
751/// Sibling expansion (default): generates two functions at the same scope level.
752///
753/// ```ignore
754/// // #[arcane] fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { body }
755/// // expands to:
756/// #[cfg(target_arch = "x86_64")]
757/// #[doc(hidden)]
758/// #[target_feature(enable = "avx2,fma,...")]
759/// #[inline]
760/// fn __arcane_process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { body }
761///
762/// #[cfg(target_arch = "x86_64")]
763/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] {
764///     unsafe { __arcane_process(token, data) }
765/// }
766/// ```
767///
768/// The sibling function is safe (Rust 2024 edition allows safe `#[target_feature]`
769/// functions). Only the call from the wrapper needs `unsafe` because the wrapper
770/// lacks matching target features. Compatible with `#![forbid(unsafe_code)]`.
771///
772/// Self/self work naturally since both functions live in the same impl scope.
773fn arcane_impl_sibling(
774    input_fn: LightFn,
775    args: &ArcaneArgs,
776    target_arch: Option<&str>,
777    token_type_name: Option<String>,
778    target_feature_attrs: Vec<Attribute>,
779    inline_attr: Attribute,
780) -> TokenStream {
781    let vis = &input_fn.vis;
782    let sig = &input_fn.sig;
783    let fn_name = &sig.ident;
784    let generics = &sig.generics;
785    let where_clause = &generics.where_clause;
786    let inputs = &sig.inputs;
787    let output = &sig.output;
788    let body = &input_fn.body;
789    // Filter out user #[inline] attrs to avoid duplicates (will become a hard error).
790    // The wrapper gets #[inline(always)] unconditionally — it's a trivial unsafe { sibling() }.
791    let attrs = filter_inline_attrs(&input_fn.attrs);
792
793    let sibling_name = format_ident!("__arcane_{}", fn_name);
794
795    // Detect self receiver
796    let has_self_receiver = inputs
797        .first()
798        .map(|arg| matches!(arg, FnArg::Receiver(_)))
799        .unwrap_or(false);
800
801    // Build sibling signature: same as original but with sibling name, #[doc(hidden)]
802    // NOT unsafe — Rust 2024 edition allows safe #[target_feature] functions.
803    // Only the call from non-matching context (the wrapper) needs unsafe.
804    let sibling_sig_inputs = inputs;
805
806    // Build turbofish for forwarding type/const generic params to sibling
807    let turbofish = build_turbofish(generics);
808
809    // Build the call from wrapper to sibling
810    let sibling_call = if has_self_receiver {
811        // Method: self.__arcane_fn::<T, N>(other_args...)
812        let other_args: Vec<proc_macro2::TokenStream> = inputs
813            .iter()
814            .skip(1) // skip self receiver
815            .filter_map(|arg| {
816                if let FnArg::Typed(pat_type) = arg
817                    && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
818                {
819                    let ident = &pat_ident.ident;
820                    Some(quote!(#ident))
821                } else {
822                    None
823                }
824            })
825            .collect();
826        quote! { self.#sibling_name #turbofish(#(#other_args),*) }
827    } else {
828        // Free function: __arcane_fn::<T, N>(all_args...)
829        let all_args: Vec<proc_macro2::TokenStream> = inputs
830            .iter()
831            .filter_map(|arg| {
832                if let FnArg::Typed(pat_type) = arg
833                    && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
834                {
835                    let ident = &pat_ident.ident;
836                    Some(quote!(#ident))
837                } else {
838                    None
839                }
840            })
841            .collect();
842        quote! { #sibling_name #turbofish(#(#all_args),*) }
843    };
844
845    // Build stub args for suppressing unused warnings
846    let stub_args: Vec<proc_macro2::TokenStream> = inputs
847        .iter()
848        .filter_map(|arg| match arg {
849            FnArg::Typed(pat_type) => {
850                if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
851                    let ident = &pat_ident.ident;
852                    Some(quote!(#ident))
853                } else {
854                    None
855                }
856            }
857            FnArg::Receiver(_) => None, // self doesn't need _ = suppression
858        })
859        .collect();
860
861    let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
862
863    let expanded = if let Some(arch) = target_arch {
864        // Sibling function: #[doc(hidden)] #[target_feature] fn __arcane_fn(...)
865        // Always private — only the wrapper is user-visible.
866        // Safe declaration — Rust 2024 allows safe #[target_feature] functions.
867        let sibling_fn = quote! {
868            #[cfg(target_arch = #arch)]
869            #[doc(hidden)]
870            #(#target_feature_attrs)*
871            #inline_attr
872            fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
873                #body
874            }
875        };
876
877        // Wrapper function: fn original_name(...) { unsafe { sibling_call } }
878        // The unsafe block is needed because the sibling has #[target_feature] and
879        // the wrapper doesn't — calling across this boundary requires unsafe.
880        let wrapper_fn = quote! {
881            #[cfg(target_arch = #arch)]
882            #(#attrs)*
883            #[inline(always)]
884            #vis #sig {
885                // SAFETY: The token parameter proves the required CPU features are available.
886                // Calling a #[target_feature] function from a non-matching context requires
887                // unsafe because the CPU may not support those instructions. The token's
888                // existence proves summon() succeeded, so the features are available.
889                unsafe { #sibling_call }
890            }
891        };
892
893        // Optional stub for other architectures
894        let stub = if args.stub {
895            quote! {
896                #[cfg(not(target_arch = #arch))]
897                #(#attrs)*
898                #vis #sig {
899                    let _ = (#(#stub_args),*);
900                    unreachable!(
901                        "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
902                         {}::summon() returns None on this architecture, so this function \
903                         is unreachable in safe code. If you used forge_token_dangerously(), \
904                         that is the bug.",
905                        stringify!(#fn_name),
906                        #token_type_str,
907                        #arch,
908                        #token_type_str,
909                    )
910                }
911            }
912        } else {
913            quote! {}
914        };
915
916        quote! {
917            #sibling_fn
918            #wrapper_fn
919            #stub
920        }
921    } else {
922        // No specific arch (trait bounds or generic) - no cfg guards, no stub needed.
923        // Still use sibling pattern for consistency. Sibling is always private.
924        let sibling_fn = quote! {
925            #[doc(hidden)]
926            #(#target_feature_attrs)*
927            #inline_attr
928            fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
929                #body
930            }
931        };
932
933        let wrapper_fn = quote! {
934            #(#attrs)*
935            #[inline(always)]
936            #vis #sig {
937                // SAFETY: The token proves the required CPU features are available.
938                unsafe { #sibling_call }
939            }
940        };
941
942        quote! {
943            #sibling_fn
944            #wrapper_fn
945        }
946    };
947
948    expanded.into()
949}
950
951/// Nested inner function expansion (opt-in via `nested` or `_self = Type`).
952///
953/// This is the original approach: generates a nested inner function inside the
954/// original function. Required when `_self = Type` is used because Self must be
955/// replaced in the nested function (where it's not in scope).
956fn arcane_impl_nested(
957    input_fn: LightFn,
958    args: &ArcaneArgs,
959    target_arch: Option<&str>,
960    token_type_name: Option<String>,
961    target_feature_attrs: Vec<Attribute>,
962    inline_attr: Attribute,
963) -> TokenStream {
964    let vis = &input_fn.vis;
965    let sig = &input_fn.sig;
966    let fn_name = &sig.ident;
967    let generics = &sig.generics;
968    let where_clause = &generics.where_clause;
969    let inputs = &sig.inputs;
970    let output = &sig.output;
971    let body = &input_fn.body;
972    // Filter out user #[inline] attrs to avoid duplicates (will become a hard error).
973    let attrs = filter_inline_attrs(&input_fn.attrs);
974
975    // Determine self receiver type if present
976    let self_receiver_kind: Option<SelfReceiver> = inputs.first().and_then(|arg| match arg {
977        FnArg::Receiver(receiver) => {
978            if receiver.reference.is_none() {
979                Some(SelfReceiver::Owned)
980            } else if receiver.mutability.is_some() {
981                Some(SelfReceiver::RefMut)
982            } else {
983                Some(SelfReceiver::Ref)
984            }
985        }
986        _ => None,
987    });
988
989    // Build inner function parameters, transforming self if needed.
990    // Also replace Self in non-self parameter types when _self = Type is set,
991    // since the inner function is a nested fn where Self from the impl is not in scope.
992    let inner_params: Vec<proc_macro2::TokenStream> = inputs
993        .iter()
994        .map(|arg| match arg {
995            FnArg::Receiver(_) => {
996                // Transform self receiver to _self parameter
997                let self_ty = args.self_type.as_ref().unwrap();
998                match self_receiver_kind.as_ref().unwrap() {
999                    SelfReceiver::Owned => quote!(_self: #self_ty),
1000                    SelfReceiver::Ref => quote!(_self: &#self_ty),
1001                    SelfReceiver::RefMut => quote!(_self: &mut #self_ty),
1002                }
1003            }
1004            FnArg::Typed(pat_type) => {
1005                if let Some(ref self_ty) = args.self_type {
1006                    replace_self_in_tokens(quote!(#pat_type), self_ty)
1007                } else {
1008                    quote!(#pat_type)
1009                }
1010            }
1011        })
1012        .collect();
1013
1014    // Build inner function call arguments
1015    let inner_args: Vec<proc_macro2::TokenStream> = inputs
1016        .iter()
1017        .filter_map(|arg| match arg {
1018            FnArg::Typed(pat_type) => {
1019                if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
1020                    let ident = &pat_ident.ident;
1021                    Some(quote!(#ident))
1022                } else {
1023                    None
1024                }
1025            }
1026            FnArg::Receiver(_) => Some(quote!(self)), // Pass self to inner as _self
1027        })
1028        .collect();
1029
1030    let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
1031
1032    // Build turbofish for forwarding type/const generic params to inner function
1033    let turbofish = build_turbofish(generics);
1034
1035    // Transform output, body, and where clause to replace Self with concrete type if needed.
1036    let (inner_output, inner_body, inner_where_clause): (
1037        proc_macro2::TokenStream,
1038        proc_macro2::TokenStream,
1039        proc_macro2::TokenStream,
1040    ) = if let Some(ref self_ty) = args.self_type {
1041        let transformed_output = replace_self_in_tokens(output.to_token_stream(), self_ty);
1042        let transformed_body = replace_self_in_tokens(body.clone(), self_ty);
1043        let transformed_where = where_clause
1044            .as_ref()
1045            .map(|wc| replace_self_in_tokens(wc.to_token_stream(), self_ty))
1046            .unwrap_or_default();
1047        (transformed_output, transformed_body, transformed_where)
1048    } else {
1049        (
1050            output.to_token_stream(),
1051            body.clone(),
1052            where_clause
1053                .as_ref()
1054                .map(|wc| wc.to_token_stream())
1055                .unwrap_or_default(),
1056        )
1057    };
1058
1059    let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
1060    let expanded = if let Some(arch) = target_arch {
1061        let stub = if args.stub {
1062            quote! {
1063                // Stub for other architectures - the token cannot be obtained
1064                #[cfg(not(target_arch = #arch))]
1065                #(#attrs)*
1066                #vis #sig {
1067                    let _ = (#(#inner_args),*);
1068                    unreachable!(
1069                        "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
1070                         {}::summon() returns None on this architecture, so this function \
1071                         is unreachable in safe code. If you used forge_token_dangerously(), \
1072                         that is the bug.",
1073                        stringify!(#fn_name),
1074                        #token_type_str,
1075                        #arch,
1076                        #token_type_str,
1077                    )
1078                }
1079            }
1080        } else {
1081            quote! {}
1082        };
1083
1084        quote! {
1085            // Real implementation for the correct architecture
1086            #[cfg(target_arch = #arch)]
1087            #(#attrs)*
1088            #[inline(always)]
1089            #vis #sig {
1090                #(#target_feature_attrs)*
1091                #inline_attr
1092                fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1093                    #inner_body
1094                }
1095
1096                // SAFETY: The token parameter proves the required CPU features are available.
1097                unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
1098            }
1099
1100            #stub
1101        }
1102    } else {
1103        // No specific arch (trait bounds or generic) - generate without cfg guards
1104        quote! {
1105            #(#attrs)*
1106            #[inline(always)]
1107            #vis #sig {
1108                #(#target_feature_attrs)*
1109                #inline_attr
1110                fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1111                    #inner_body
1112                }
1113
1114                // SAFETY: The token proves the required CPU features are available.
1115                unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
1116            }
1117        }
1118    };
1119
1120    expanded.into()
1121}
1122
1123/// Mark a function as an arcane SIMD function.
1124///
1125/// This macro generates a safe wrapper around a `#[target_feature]` function.
1126/// The token parameter type determines which CPU features are enabled.
1127///
1128/// # Expansion Modes
1129///
1130/// ## Sibling (default)
1131///
1132/// Generates two functions at the same scope: a safe `#[target_feature]` sibling
1133/// and a safe wrapper. `self`/`Self` work naturally since both functions share scope.
1134/// Compatible with `#![forbid(unsafe_code)]`.
1135///
1136/// ```ignore
1137/// #[arcane]
1138/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { /* body */ }
1139/// // Expands to (x86_64 only):
1140/// #[cfg(target_arch = "x86_64")]
1141/// #[doc(hidden)]
1142/// #[target_feature(enable = "avx2,fma,...")]
1143/// fn __arcane_process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { /* body */ }
1144///
1145/// #[cfg(target_arch = "x86_64")]
1146/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] {
1147///     unsafe { __arcane_process(token, data) }
1148/// }
1149/// ```
1150///
1151/// Methods work naturally:
1152///
1153/// ```ignore
1154/// impl MyType {
1155///     #[arcane]
1156///     fn compute(&self, token: X64V3Token) -> f32 {
1157///         self.data.iter().sum()  // self/Self just work!
1158///     }
1159/// }
1160/// ```
1161///
1162/// ## Nested (`nested` or `_self = Type`)
1163///
1164/// Generates a nested inner function inside the original. Required for trait impls
1165/// (where sibling functions would fail) and when `_self = Type` is used.
1166///
1167/// ```ignore
1168/// impl SimdOps for MyType {
1169///     #[arcane(_self = MyType)]
1170///     fn compute(&self, token: X64V3Token) -> Self {
1171///         // Use _self instead of self, Self replaced with MyType
1172///         _self.data.iter().sum()
1173///     }
1174/// }
1175/// ```
1176///
1177/// # Cross-Architecture Behavior
1178///
1179/// **Default (cfg-out):** On the wrong architecture, the function is not emitted
1180/// at all — no stub, no dead code. Code that references it must be cfg-gated.
1181///
1182/// **With `stub`:** Generates an `unreachable!()` stub on wrong architectures.
1183/// Use when cross-arch dispatch references the function without cfg guards.
1184///
1185/// ```ignore
1186/// #[arcane(stub)]  // generates stub on wrong arch
1187/// fn process_neon(token: NeonToken, data: &[f32]) -> f32 { ... }
1188/// ```
1189///
1190/// `incant!` is unaffected — it already cfg-gates dispatch calls by architecture.
1191///
1192/// # Token Parameter Forms
1193///
1194/// ```ignore
1195/// // Concrete token
1196/// #[arcane]
1197/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { ... }
1198///
1199/// // impl Trait bound
1200/// #[arcane]
1201/// fn process(token: impl HasX64V2, data: &[f32; 8]) -> [f32; 8] { ... }
1202///
1203/// // Generic with inline or where-clause bounds
1204/// #[arcane]
1205/// fn process<T: HasX64V2>(token: T, data: &[f32; 8]) -> [f32; 8] { ... }
1206///
1207/// // Wildcard
1208/// #[arcane]
1209/// fn process(_: X64V3Token, data: &[f32; 8]) -> [f32; 8] { ... }
1210/// ```
1211///
1212/// # Options
1213///
1214/// | Option | Effect |
1215/// |--------|--------|
1216/// | `stub` | Generate `unreachable!()` stub on wrong architecture |
1217/// | `nested` | Use nested inner function instead of sibling |
1218/// | `_self = Type` | Implies `nested`, transforms self receiver, replaces Self |
1219/// | `inline_always` | Use `#[inline(always)]` (requires nightly) |
1220/// | `import_intrinsics` | Auto-import `archmage::intrinsics::{arch}::*` (includes safe memory ops) |
1221/// | `import_magetypes` | Auto-import `magetypes::simd::{ns}::*` and `magetypes::simd::backends::*` |
1222///
1223/// ## Auto-Imports
1224///
1225/// `import_intrinsics` and `import_magetypes` inject `use` statements into the
1226/// function body, eliminating boilerplate. The macro derives the architecture and
1227/// namespace from the token type:
1228///
1229/// ```ignore
1230/// // Without auto-imports — lots of boilerplate:
1231/// use std::arch::x86_64::*;
1232/// use magetypes::simd::v3::*;
1233///
1234/// #[arcane]
1235/// fn process(token: X64V3Token, data: &[f32; 8]) -> f32 {
1236///     let v = f32x8::load(token, data);
1237///     let zero = _mm256_setzero_ps();
1238///     // ...
1239/// }
1240///
1241/// // With auto-imports — clean:
1242/// #[arcane(import_intrinsics, import_magetypes)]
1243/// fn process(token: X64V3Token, data: &[f32; 8]) -> f32 {
1244///     let v = f32x8::load(token, data);
1245///     let zero = _mm256_setzero_ps();
1246///     // ...
1247/// }
1248/// ```
1249///
1250/// The namespace mapping is token-driven:
1251///
1252/// | Token | `import_intrinsics` | `import_magetypes` |
1253/// |-------|--------------------|--------------------|
1254/// | `X64V1..V3Token` | `archmage::intrinsics::x86_64::*` | `magetypes::simd::v3::*` |
1255/// | `X64V4Token` | `archmage::intrinsics::x86_64::*` | `magetypes::simd::v4::*` |
1256/// | `X64V4xToken` | `archmage::intrinsics::x86_64::*` | `magetypes::simd::v4x::*` |
1257/// | `NeonToken` / ARM | `archmage::intrinsics::aarch64::*` | `magetypes::simd::neon::*` |
1258/// | `Wasm128Token` | `archmage::intrinsics::wasm32::*` | `magetypes::simd::wasm128::*` |
1259///
1260/// Works with concrete tokens, `impl Trait` bounds, and generic parameters.
1261///
1262/// # Supported Tokens
1263///
1264/// - **x86_64**: `X64V2Token`, `X64V3Token`/`Desktop64`, `X64V4Token`/`Avx512Token`/`Server64`,
1265///   `X64V4xToken`, `Avx512Fp16Token`, `X64CryptoToken`, `X64V3CryptoToken`
1266/// - **ARM**: `NeonToken`/`Arm64`, `Arm64V2Token`, `Arm64V3Token`,
1267///   `NeonAesToken`, `NeonSha3Token`, `NeonCrcToken`
1268/// - **WASM**: `Wasm128Token`
1269///
1270/// # Supported Trait Bounds
1271///
1272/// `HasX64V2`, `HasX64V4`, `HasNeon`, `HasNeonAes`, `HasNeonSha3`, `HasArm64V2`, `HasArm64V3`
1273///
1274/// ```ignore
1275/// #![feature(target_feature_inline_always)]
1276///
1277/// #[arcane(inline_always)]
1278/// fn fast_kernel(token: Avx2Token, data: &mut [f32]) {
1279///     // Inner function will use #[inline(always)]
1280/// }
1281/// ```
1282#[proc_macro_attribute]
1283pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
1284    let args = parse_macro_input!(attr as ArcaneArgs);
1285    let input_fn = parse_macro_input!(item as LightFn);
1286    arcane_impl(input_fn, "arcane", args)
1287}
1288
1289/// Legacy alias for [`arcane`].
1290///
1291/// **Deprecated:** Use `#[arcane]` instead. This alias exists only for migration.
1292#[proc_macro_attribute]
1293#[doc(hidden)]
1294pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
1295    let args = parse_macro_input!(attr as ArcaneArgs);
1296    let input_fn = parse_macro_input!(item as LightFn);
1297    arcane_impl(input_fn, "simd_fn", args)
1298}
1299
1300/// Descriptive alias for [`arcane`].
1301///
1302/// Generates a safe wrapper around a `#[target_feature]` inner function.
1303/// The token type in your signature determines which CPU features are enabled.
1304/// Creates an LLVM optimization boundary — use [`token_target_features`]
1305/// (alias for [`rite`]) for inner helpers to avoid this.
1306///
1307/// Since Rust 1.85, value-based SIMD intrinsics are safe inside
1308/// `#[target_feature]` functions. This macro generates the `#[target_feature]`
1309/// wrapper so you never need to write `unsafe` for SIMD code.
1310///
1311/// See [`arcane`] for full documentation and examples.
1312#[proc_macro_attribute]
1313pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
1314    let args = parse_macro_input!(attr as ArcaneArgs);
1315    let input_fn = parse_macro_input!(item as LightFn);
1316    arcane_impl(input_fn, "token_target_features_boundary", args)
1317}
1318
1319// ============================================================================
1320// Rite macro for inner SIMD functions (inlines into matching #[target_feature] callers)
1321// ============================================================================
1322
1323/// Annotate inner SIMD helpers called from `#[arcane]` functions.
1324///
1325/// Unlike `#[arcane]`, which creates an inner `#[target_feature]` function behind
1326/// a safe boundary, `#[rite]` adds `#[target_feature]` and `#[inline]` directly.
1327/// LLVM inlines it into any caller with matching features — no boundary crossing.
1328///
1329/// # Three Modes
1330///
1331/// **Token-based:** Reads the token type from the function signature.
1332/// ```ignore
1333/// #[rite]
1334/// fn helper(_: X64V3Token, v: __m256) -> __m256 { _mm256_add_ps(v, v) }
1335/// ```
1336///
1337/// **Tier-based:** Specify the tier name directly, no token parameter needed.
1338/// ```ignore
1339/// #[rite(v3)]
1340/// fn helper(v: __m256) -> __m256 { _mm256_add_ps(v, v) }
1341/// ```
1342///
1343/// Both produce identical code. The token form can be easier to remember if
1344/// you already have the token in scope.
1345///
1346/// **Multi-tier:** Specify multiple tiers to generate suffixed variants.
1347/// ```ignore
1348/// #[rite(v3, v4)]
1349/// fn process(data: &[f32; 4]) -> f32 { data.iter().sum() }
1350/// // Generates: process_v3() and process_v4()
1351/// ```
1352///
1353/// Each variant gets its own `#[target_feature]` and `#[cfg(target_arch)]`.
1354/// Since Rust 1.85, calling these from a matching `#[arcane]` or `#[rite]`
1355/// context is safe — no `unsafe` needed when the caller has matching or
1356/// superset features.
1357///
1358/// # Safety
1359///
1360/// `#[rite]` functions can only be safely called from contexts where the
1361/// required CPU features are enabled:
1362/// - From within `#[arcane]` functions with matching/superset tokens
1363/// - From within other `#[rite]` functions with matching/superset tokens
1364/// - From code compiled with `-Ctarget-cpu` that enables the features
1365///
1366/// Calling from other contexts requires `unsafe` and the caller must ensure
1367/// the CPU supports the required features.
1368///
1369/// # Cross-Architecture Behavior
1370///
1371/// Like `#[arcane]`, defaults to cfg-out (no function on wrong arch).
1372/// Use `#[rite(stub)]` to generate an unreachable stub instead.
1373///
1374/// # Options
1375///
1376/// | Option | Effect |
1377/// |--------|--------|
1378/// | tier name(s) | `v3`, `neon`, etc. One = single function; multiple = suffixed variants |
1379/// | `stub` | Generate `unreachable!()` stub on wrong architecture |
1380/// | `import_intrinsics` | Auto-import `archmage::intrinsics::{arch}::*` (includes safe memory ops) |
1381/// | `import_magetypes` | Auto-import `magetypes::simd::{ns}::*` and `magetypes::simd::backends::*` |
1382///
1383/// See `#[arcane]` docs for the full namespace mapping table.
1384///
1385/// # Comparison with #[arcane]
1386///
1387/// | Aspect | `#[arcane]` | `#[rite]` |
1388/// |--------|-------------|-----------|
1389/// | Creates wrapper | Yes | No |
1390/// | Entry point | Yes | No |
1391/// | Inlines into caller | No (barrier) | Yes |
1392/// | Safe to call anywhere | Yes (with token) | Only from feature-enabled context |
1393/// | Multi-tier variants | No | Yes (`#[rite(v3, v4, neon)]`) |
1394/// | `stub` param | Yes | Yes |
1395/// | `import_intrinsics` | Yes | Yes |
1396/// | `import_magetypes` | Yes | Yes |
1397#[proc_macro_attribute]
1398pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
1399    let args = parse_macro_input!(attr as RiteArgs);
1400    let input_fn = parse_macro_input!(item as LightFn);
1401    rite_impl(input_fn, args)
1402}
1403
1404/// Descriptive alias for [`rite`].
1405///
1406/// Applies `#[target_feature]` + `#[inline]` based on the token type in your
1407/// function signature. No wrapper, no optimization boundary. Use for functions
1408/// called from within `#[arcane]`/`#[token_target_features_boundary]` code.
1409///
1410/// Since Rust 1.85, calling a `#[target_feature]` function from another function
1411/// with matching features is safe — no `unsafe` needed.
1412///
1413/// See [`rite`] for full documentation and examples.
1414#[proc_macro_attribute]
1415pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
1416    let args = parse_macro_input!(attr as RiteArgs);
1417    let input_fn = parse_macro_input!(item as LightFn);
1418    rite_impl(input_fn, args)
1419}
1420
1421/// Arguments for the `#[rite]` macro.
1422#[derive(Default)]
1423struct RiteArgs {
1424    /// Generate an `unreachable!()` stub on the wrong architecture.
1425    /// Default is false (cfg-out: no function emitted on wrong arch).
1426    stub: bool,
1427    /// Inject `use archmage::intrinsics::{arch}::*;` (includes safe memory ops).
1428    import_intrinsics: bool,
1429    /// Inject `use magetypes::simd::{ns}::*;`, `use magetypes::simd::generic::*;`,
1430    /// and `use magetypes::simd::backends::*;`.
1431    import_magetypes: bool,
1432    /// Tiers specified directly (e.g., `#[rite(v3)]` or `#[rite(v3, v4, neon)]`).
1433    /// Stored as canonical token names (e.g., "X64V3Token").
1434    /// Single tier: generates one function (no suffix, no token parameter needed).
1435    /// Multiple tiers: generates suffixed variants (e.g., `fn_v3`, `fn_v4`, `fn_neon`).
1436    tier_tokens: Vec<String>,
1437}
1438
1439impl Parse for RiteArgs {
1440    fn parse(input: ParseStream) -> syn::Result<Self> {
1441        let mut args = RiteArgs::default();
1442
1443        while !input.is_empty() {
1444            let ident: Ident = input.parse()?;
1445            match ident.to_string().as_str() {
1446                "stub" => args.stub = true,
1447                "import_intrinsics" => args.import_intrinsics = true,
1448                "import_magetypes" => args.import_magetypes = true,
1449                other => {
1450                    if let Some(canonical) = tier_to_canonical_token(other) {
1451                        args.tier_tokens.push(String::from(canonical));
1452                    } else {
1453                        return Err(syn::Error::new(
1454                            ident.span(),
1455                            format!(
1456                                "unknown rite argument: `{}`. Supported: tier names \
1457                                 (v1, v2, v3, v4, neon, arm_v2, wasm128, ...), \
1458                                 `stub`, `import_intrinsics`, `import_magetypes`.",
1459                                other
1460                            ),
1461                        ));
1462                    }
1463                }
1464            }
1465            if input.peek(Token![,]) {
1466                let _: Token![,] = input.parse()?;
1467            }
1468        }
1469
1470        Ok(args)
1471    }
1472}
1473
1474/// Implementation for the `#[rite]` macro.
1475fn rite_impl(input_fn: LightFn, args: RiteArgs) -> TokenStream {
1476    // Multi-tier mode: generate suffixed variants for each tier
1477    if args.tier_tokens.len() > 1 {
1478        return rite_multi_tier_impl(input_fn, &args);
1479    }
1480
1481    // Single-tier or token-param mode
1482    rite_single_impl(input_fn, args)
1483}
1484
1485/// Generate a single `#[rite]` function (single tier or token-param mode).
1486fn rite_single_impl(mut input_fn: LightFn, args: RiteArgs) -> TokenStream {
1487    // Resolve features: either from tier name or from token parameter
1488    let TokenParamInfo {
1489        features,
1490        target_arch,
1491        magetypes_namespace,
1492        ..
1493    } = if let Some(tier_token) = args.tier_tokens.first() {
1494        // Tier specified directly (e.g., #[rite(v3)]) — no token param needed
1495        let features = token_to_features(tier_token)
1496            .expect("tier_to_canonical_token returned invalid token name")
1497            .to_vec();
1498        let target_arch = token_to_arch(tier_token);
1499        let magetypes_namespace = token_to_magetypes_namespace(tier_token);
1500        TokenParamInfo {
1501            ident: Ident::new("_", proc_macro2::Span::call_site()),
1502            features,
1503            target_arch,
1504            token_type_name: Some(tier_token.clone()),
1505            magetypes_namespace,
1506        }
1507    } else {
1508        match find_token_param(&input_fn.sig) {
1509            Some(result) => result,
1510            None => {
1511                // Check for specific misuse: featureless traits like SimdToken
1512                if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
1513                    let msg = format!(
1514                        "`{trait_name}` cannot be used as a token bound in #[rite] \
1515                         because it doesn't specify any CPU features.\n\
1516                         \n\
1517                         #[rite] needs concrete features to generate #[target_feature]. \
1518                         Use a concrete token, a feature trait, or a tier name:\n\
1519                         \n\
1520                         Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
1521                         Feature traits:  impl HasX64V2, impl HasNeon, impl HasArm64V3, ...\n\
1522                         Tier names:      #[rite(v3)], #[rite(neon)], #[rite(v4)], ..."
1523                    );
1524                    return syn::Error::new_spanned(&input_fn.sig, msg)
1525                        .to_compile_error()
1526                        .into();
1527                }
1528                let msg = "rite requires a token parameter or a tier name. Supported forms:\n\
1529                     - Tier name: `#[rite(v3)]`, `#[rite(neon)]`\n\
1530                     - Multi-tier: `#[rite(v3, v4, neon)]` (generates suffixed variants)\n\
1531                     - Concrete: `token: X64V3Token`\n\
1532                     - impl Trait: `token: impl HasX64V2`\n\
1533                     - Generic: `fn foo<T: HasX64V2>(token: T, ...)`";
1534                return syn::Error::new_spanned(&input_fn.sig, msg)
1535                    .to_compile_error()
1536                    .into();
1537            }
1538        }
1539    };
1540
1541    // Build target_feature attributes
1542    let target_feature_attrs: Vec<Attribute> = features
1543        .iter()
1544        .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1545        .collect();
1546
1547    // Always use #[inline] - #[inline(always)] + #[target_feature] requires nightly
1548    let inline_attr: Attribute = parse_quote!(#[inline]);
1549
1550    // Prepend attributes to the function, filtering user #[inline] to avoid duplicates
1551    let mut new_attrs = target_feature_attrs;
1552    new_attrs.push(inline_attr);
1553    for attr in filter_inline_attrs(&input_fn.attrs) {
1554        new_attrs.push(attr.clone());
1555    }
1556    input_fn.attrs = new_attrs;
1557
1558    // Prepend import statements to body if requested
1559    let body_imports = generate_imports(
1560        target_arch,
1561        magetypes_namespace,
1562        args.import_intrinsics,
1563        args.import_magetypes,
1564    );
1565    if !body_imports.is_empty() {
1566        let original_body = &input_fn.body;
1567        input_fn.body = quote! {
1568            #body_imports
1569            #original_body
1570        };
1571    }
1572
1573    // If we know the target arch, generate cfg-gated impl (+ optional stub)
1574    if let Some(arch) = target_arch {
1575        let vis = &input_fn.vis;
1576        let sig = &input_fn.sig;
1577        let attrs = &input_fn.attrs;
1578        let body = &input_fn.body;
1579
1580        let stub = if args.stub {
1581            quote! {
1582                #[cfg(not(target_arch = #arch))]
1583                #vis #sig {
1584                    unreachable!(concat!(
1585                        "This function requires ",
1586                        #arch,
1587                        " architecture"
1588                    ))
1589                }
1590            }
1591        } else {
1592            quote! {}
1593        };
1594
1595        quote! {
1596            #[cfg(target_arch = #arch)]
1597            #(#attrs)*
1598            #vis #sig {
1599                #body
1600            }
1601
1602            #stub
1603        }
1604        .into()
1605    } else {
1606        // No specific arch (trait bounds) - just emit the annotated function
1607        quote!(#input_fn).into()
1608    }
1609}
1610
1611/// Generate multiple suffixed `#[rite]` variants for multi-tier mode.
1612///
1613/// `#[rite(v3, v4, neon)]` on `fn process(...)` generates:
1614/// - `fn process_v3(...)` with `#[target_feature(enable = "avx2,fma,...")]`
1615/// - `fn process_v4(...)` with `#[target_feature(enable = "avx512f,...")]`
1616/// - `fn process_neon(...)` with `#[target_feature(enable = "neon")]`
1617///
1618/// Each variant is cfg-gated to its architecture and gets `#[inline]`.
1619fn rite_multi_tier_impl(input_fn: LightFn, args: &RiteArgs) -> TokenStream {
1620    let fn_name = &input_fn.sig.ident;
1621    let mut variants = proc_macro2::TokenStream::new();
1622
1623    for tier_token in &args.tier_tokens {
1624        let features = match token_to_features(tier_token) {
1625            Some(f) => f,
1626            None => {
1627                return syn::Error::new_spanned(
1628                    &input_fn.sig,
1629                    format!("unknown token `{tier_token}` in multi-tier #[rite]"),
1630                )
1631                .to_compile_error()
1632                .into();
1633            }
1634        };
1635        let target_arch = token_to_arch(tier_token);
1636        let magetypes_namespace = token_to_magetypes_namespace(tier_token);
1637        let suffix = canonical_token_to_tier_suffix(tier_token)
1638            .expect("canonical token must have a tier suffix");
1639
1640        // Build suffixed function name
1641        let suffixed_ident = format_ident!("{}_{}", fn_name, suffix);
1642
1643        // Clone and rename the function
1644        let mut variant_fn = input_fn.clone();
1645        variant_fn.sig.ident = suffixed_ident;
1646
1647        // Build target_feature attributes
1648        let target_feature_attrs: Vec<Attribute> = features
1649            .iter()
1650            .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1651            .collect();
1652        let inline_attr: Attribute = parse_quote!(#[inline]);
1653
1654        let mut new_attrs = target_feature_attrs;
1655        new_attrs.push(inline_attr);
1656        for attr in filter_inline_attrs(&variant_fn.attrs) {
1657            new_attrs.push(attr.clone());
1658        }
1659        variant_fn.attrs = new_attrs;
1660
1661        // Prepend import statements if requested
1662        let body_imports = generate_imports(
1663            target_arch,
1664            magetypes_namespace,
1665            args.import_intrinsics,
1666            args.import_magetypes,
1667        );
1668        if !body_imports.is_empty() {
1669            let original_body = &variant_fn.body;
1670            variant_fn.body = quote! {
1671                #body_imports
1672                #original_body
1673            };
1674        }
1675
1676        // Emit cfg-gated variant
1677        if let Some(arch) = target_arch {
1678            let vis = &variant_fn.vis;
1679            let sig = &variant_fn.sig;
1680            let attrs = &variant_fn.attrs;
1681            let body = &variant_fn.body;
1682
1683            variants.extend(quote! {
1684                #[cfg(target_arch = #arch)]
1685                #(#attrs)*
1686                #vis #sig {
1687                    #body
1688                }
1689            });
1690
1691            if args.stub {
1692                variants.extend(quote! {
1693                    #[cfg(not(target_arch = #arch))]
1694                    #vis #sig {
1695                        unreachable!(concat!(
1696                            "This function requires ",
1697                            #arch,
1698                            " architecture"
1699                        ))
1700                    }
1701                });
1702            }
1703        } else {
1704            // No specific arch — just emit the annotated function
1705            variants.extend(quote!(#variant_fn));
1706        }
1707    }
1708
1709    variants.into()
1710}
1711
1712// =============================================================================
1713// magetypes! macro - generate platform variants from generic function
1714// =============================================================================
1715
1716/// Generate platform-specific variants from a function by replacing `Token`.
1717///
1718/// Use `Token` as a placeholder for the token type. The macro generates
1719/// suffixed variants with `Token` replaced by the concrete token type, and
1720/// each variant wrapped in the appropriate `#[cfg(target_arch = ...)]` guard.
1721///
1722/// # Default tiers
1723///
1724/// Without arguments, generates `_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`:
1725///
1726/// ```rust,ignore
1727/// #[magetypes]
1728/// fn process(token: Token, data: &[f32]) -> f32 {
1729///     inner_simd_work(token, data)
1730/// }
1731/// ```
1732///
1733/// # Explicit tiers
1734///
1735/// Specify which tiers to generate:
1736///
1737/// ```rust,ignore
1738/// #[magetypes(v1, v3, neon)]
1739/// fn process(token: Token, data: &[f32]) -> f32 {
1740///     inner_simd_work(token, data)
1741/// }
1742/// // Generates: process_v1, process_v3, process_neon, process_scalar
1743/// ```
1744///
1745/// `scalar` is always included implicitly.
1746///
1747/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
1748/// `neon_sha3`, `neon_crc`, `wasm128`, `wasm128_relaxed`, `scalar`.
1749///
1750/// # What gets replaced
1751///
1752/// **Only `Token`** is replaced — with the concrete token type for each variant
1753/// (e.g., `archmage::X64V3Token`, `archmage::ScalarToken`). SIMD types like
1754/// `f32x8` and constants like `LANES` are **not** replaced by this macro.
1755///
1756/// # Usage with incant!
1757///
1758/// The generated variants work with `incant!` for dispatch:
1759///
1760/// ```rust,ignore
1761/// pub fn process_api(data: &[f32]) -> f32 {
1762///     incant!(process(data))
1763/// }
1764///
1765/// // Or with matching explicit tiers:
1766/// pub fn process_api(data: &[f32]) -> f32 {
1767///     incant!(process(data), [v1, v3, neon, scalar])
1768/// }
1769/// ```
1770#[proc_macro_attribute]
1771pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
1772    let input_fn = parse_macro_input!(item as LightFn);
1773
1774    // Parse optional tier list from attribute args
1775    let tier_names: Vec<String> = if attr.is_empty() {
1776        DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
1777    } else {
1778        let parser = |input: ParseStream| input.parse_terminated(Ident::parse, Token![,]);
1779        let idents = match syn::parse::Parser::parse(parser, attr) {
1780            Ok(p) => p,
1781            Err(e) => return e.to_compile_error().into(),
1782        };
1783        idents.iter().map(|i| i.to_string()).collect()
1784    };
1785
1786    let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
1787        Ok(t) => t,
1788        Err(e) => return e.to_compile_error().into(),
1789    };
1790
1791    magetypes_impl(input_fn, &tiers)
1792}
1793
1794fn magetypes_impl(mut input_fn: LightFn, tiers: &[&TierDescriptor]) -> TokenStream {
1795    // Strip user-provided #[arcane] / #[rite] to prevent double-wrapping
1796    // (magetypes auto-adds #[arcane] on non-scalar variants)
1797    input_fn
1798        .attrs
1799        .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
1800
1801    let fn_name = &input_fn.sig.ident;
1802    let fn_attrs = &input_fn.attrs;
1803
1804    // Convert function to string for text substitution
1805    let fn_str = input_fn.to_token_stream().to_string();
1806
1807    let mut variants = Vec::new();
1808
1809    for tier in tiers {
1810        // Create suffixed function name
1811        let suffixed_name = format!("{}_{}", fn_name, tier.suffix);
1812
1813        // Do text substitution
1814        let mut variant_str = fn_str.clone();
1815
1816        // Replace function name
1817        variant_str = variant_str.replacen(&fn_name.to_string(), &suffixed_name, 1);
1818
1819        // Replace Token type with concrete token
1820        variant_str = variant_str.replace("Token", tier.token_path);
1821
1822        // Parse back to tokens
1823        let variant_tokens: proc_macro2::TokenStream = match variant_str.parse() {
1824            Ok(t) => t,
1825            Err(e) => {
1826                return syn::Error::new_spanned(
1827                    &input_fn,
1828                    format!(
1829                        "Failed to parse generated variant `{}`: {}",
1830                        suffixed_name, e
1831                    ),
1832                )
1833                .to_compile_error()
1834                .into();
1835            }
1836        };
1837
1838        // Add cfg guards
1839        let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
1840            (Some(arch), Some(feature)) => {
1841                quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
1842            }
1843            (Some(arch), None) => {
1844                quote! { #[cfg(target_arch = #arch)] }
1845            }
1846            (None, Some(feature)) => {
1847                quote! { #[cfg(feature = #feature)] }
1848            }
1849            (None, None) => {
1850                quote! {} // No guard needed (scalar)
1851            }
1852        };
1853
1854        variants.push(if tier.name != "scalar" {
1855            // Non-scalar variants get #[arcane] so target_feature is applied
1856            quote! {
1857                #cfg_guard
1858                #[archmage::arcane]
1859                #variant_tokens
1860            }
1861        } else {
1862            quote! {
1863                #cfg_guard
1864                #variant_tokens
1865            }
1866        });
1867    }
1868
1869    // Remove attributes from the list that should not be duplicated
1870    let filtered_attrs: Vec<_> = fn_attrs
1871        .iter()
1872        .filter(|a| !a.path().is_ident("magetypes"))
1873        .collect();
1874
1875    let output = quote! {
1876        #(#filtered_attrs)*
1877        #(#variants)*
1878    };
1879
1880    output.into()
1881}
1882
1883// =============================================================================
1884// incant! macro - dispatch to platform-specific variants
1885// =============================================================================
1886
1887// =============================================================================
1888// Tier descriptors for incant! and #[magetypes]
1889// =============================================================================
1890
1891/// Describes a dispatch tier for incant! and #[magetypes].
1892struct TierDescriptor {
1893    /// Tier name as written in user code (e.g., "v3", "neon")
1894    name: &'static str,
1895    /// Function suffix (e.g., "v3", "neon", "scalar")
1896    suffix: &'static str,
1897    /// Token type path (e.g., "archmage::X64V3Token")
1898    token_path: &'static str,
1899    /// IntoConcreteToken method name (e.g., "as_x64v3")
1900    as_method: &'static str,
1901    /// Target architecture for cfg guard (None = no guard)
1902    target_arch: Option<&'static str>,
1903    /// Required cargo feature (None = no feature guard)
1904    cargo_feature: Option<&'static str>,
1905    /// Dispatch priority (higher = tried first within same arch)
1906    priority: u32,
1907}
1908
1909/// All known tiers in dispatch-priority order (highest first within arch).
1910const ALL_TIERS: &[TierDescriptor] = &[
1911    // x86: highest to lowest
1912    TierDescriptor {
1913        name: "v4x",
1914        suffix: "v4x",
1915        token_path: "archmage::X64V4xToken",
1916        as_method: "as_x64v4x",
1917        target_arch: Some("x86_64"),
1918        cargo_feature: Some("avx512"),
1919        priority: 50,
1920    },
1921    TierDescriptor {
1922        name: "v4",
1923        suffix: "v4",
1924        token_path: "archmage::X64V4Token",
1925        as_method: "as_x64v4",
1926        target_arch: Some("x86_64"),
1927        cargo_feature: Some("avx512"),
1928        priority: 40,
1929    },
1930    TierDescriptor {
1931        name: "v3_crypto",
1932        suffix: "v3_crypto",
1933        token_path: "archmage::X64V3CryptoToken",
1934        as_method: "as_x64v3_crypto",
1935        target_arch: Some("x86_64"),
1936        cargo_feature: None,
1937        priority: 35,
1938    },
1939    TierDescriptor {
1940        name: "v3",
1941        suffix: "v3",
1942        token_path: "archmage::X64V3Token",
1943        as_method: "as_x64v3",
1944        target_arch: Some("x86_64"),
1945        cargo_feature: None,
1946        priority: 30,
1947    },
1948    TierDescriptor {
1949        name: "x64_crypto",
1950        suffix: "x64_crypto",
1951        token_path: "archmage::X64CryptoToken",
1952        as_method: "as_x64_crypto",
1953        target_arch: Some("x86_64"),
1954        cargo_feature: None,
1955        priority: 25,
1956    },
1957    TierDescriptor {
1958        name: "v2",
1959        suffix: "v2",
1960        token_path: "archmage::X64V2Token",
1961        as_method: "as_x64v2",
1962        target_arch: Some("x86_64"),
1963        cargo_feature: None,
1964        priority: 20,
1965    },
1966    TierDescriptor {
1967        name: "v1",
1968        suffix: "v1",
1969        token_path: "archmage::X64V1Token",
1970        as_method: "as_x64v1",
1971        target_arch: Some("x86_64"),
1972        cargo_feature: None,
1973        priority: 10,
1974    },
1975    // ARM: highest to lowest
1976    TierDescriptor {
1977        name: "arm_v3",
1978        suffix: "arm_v3",
1979        token_path: "archmage::Arm64V3Token",
1980        as_method: "as_arm_v3",
1981        target_arch: Some("aarch64"),
1982        cargo_feature: None,
1983        priority: 50,
1984    },
1985    TierDescriptor {
1986        name: "arm_v2",
1987        suffix: "arm_v2",
1988        token_path: "archmage::Arm64V2Token",
1989        as_method: "as_arm_v2",
1990        target_arch: Some("aarch64"),
1991        cargo_feature: None,
1992        priority: 40,
1993    },
1994    TierDescriptor {
1995        name: "neon_aes",
1996        suffix: "neon_aes",
1997        token_path: "archmage::NeonAesToken",
1998        as_method: "as_neon_aes",
1999        target_arch: Some("aarch64"),
2000        cargo_feature: None,
2001        priority: 30,
2002    },
2003    TierDescriptor {
2004        name: "neon_sha3",
2005        suffix: "neon_sha3",
2006        token_path: "archmage::NeonSha3Token",
2007        as_method: "as_neon_sha3",
2008        target_arch: Some("aarch64"),
2009        cargo_feature: None,
2010        priority: 30,
2011    },
2012    TierDescriptor {
2013        name: "neon_crc",
2014        suffix: "neon_crc",
2015        token_path: "archmage::NeonCrcToken",
2016        as_method: "as_neon_crc",
2017        target_arch: Some("aarch64"),
2018        cargo_feature: None,
2019        priority: 30,
2020    },
2021    TierDescriptor {
2022        name: "neon",
2023        suffix: "neon",
2024        token_path: "archmage::NeonToken",
2025        as_method: "as_neon",
2026        target_arch: Some("aarch64"),
2027        cargo_feature: None,
2028        priority: 20,
2029    },
2030    // WASM
2031    TierDescriptor {
2032        name: "wasm128_relaxed",
2033        suffix: "wasm128_relaxed",
2034        token_path: "archmage::Wasm128RelaxedToken",
2035        as_method: "as_wasm128_relaxed",
2036        target_arch: Some("wasm32"),
2037        cargo_feature: None,
2038        priority: 21,
2039    },
2040    TierDescriptor {
2041        name: "wasm128",
2042        suffix: "wasm128",
2043        token_path: "archmage::Wasm128Token",
2044        as_method: "as_wasm128",
2045        target_arch: Some("wasm32"),
2046        cargo_feature: None,
2047        priority: 20,
2048    },
2049    // Scalar (always last)
2050    TierDescriptor {
2051        name: "scalar",
2052        suffix: "scalar",
2053        token_path: "archmage::ScalarToken",
2054        as_method: "as_scalar",
2055        target_arch: None,
2056        cargo_feature: None,
2057        priority: 0,
2058    },
2059];
2060
2061/// Default tiers (backwards-compatible with pre-explicit behavior).
2062const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
2063
2064/// Whether `incant!` requires `scalar` in explicit tier lists.
2065/// Currently false for backwards compatibility. Flip to true in v1.0.
2066const REQUIRE_EXPLICIT_SCALAR: bool = false;
2067
2068/// Look up a tier by name, returning an error on unknown names.
2069fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
2070    ALL_TIERS.iter().find(|t| t.name == name)
2071}
2072
2073/// Resolve tier names to descriptors, sorted by dispatch priority (highest first).
2074/// Always appends "scalar" if not already present.
2075fn resolve_tiers(
2076    tier_names: &[String],
2077    error_span: proc_macro2::Span,
2078) -> syn::Result<Vec<&'static TierDescriptor>> {
2079    let mut tiers = Vec::new();
2080    for name in tier_names {
2081        match find_tier(name) {
2082            Some(tier) => tiers.push(tier),
2083            None => {
2084                let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
2085                return Err(syn::Error::new(
2086                    error_span,
2087                    format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
2088                ));
2089            }
2090        }
2091    }
2092
2093    // Always include scalar fallback
2094    if !tiers.iter().any(|t| t.name == "scalar") {
2095        tiers.push(find_tier("scalar").unwrap());
2096    }
2097
2098    // Sort by priority (highest first) for correct dispatch order
2099    tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
2100
2101    Ok(tiers)
2102}
2103
2104// =============================================================================
2105// incant! macro - dispatch to platform-specific variants
2106// =============================================================================
2107
2108/// Input for the incant! macro
2109struct IncantInput {
2110    /// Function path to call (e.g. `func` or `module::func`)
2111    func_path: syn::Path,
2112    /// Arguments to pass
2113    args: Vec<syn::Expr>,
2114    /// Optional token variable for passthrough mode
2115    with_token: Option<syn::Expr>,
2116    /// Optional explicit tier list (None = default tiers)
2117    tiers: Option<(Vec<String>, proc_macro2::Span)>,
2118}
2119
2120/// Create a suffixed version of a function path.
2121/// e.g. `module::func` + `"v3"` → `module::func_v3`
2122fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
2123    let mut suffixed = path.clone();
2124    if let Some(last) = suffixed.segments.last_mut() {
2125        last.ident = format_ident!("{}_{}", last.ident, suffix);
2126    }
2127    suffixed
2128}
2129
2130impl Parse for IncantInput {
2131    fn parse(input: ParseStream) -> syn::Result<Self> {
2132        // Parse: function_path(arg1, arg2, ...) [with token_expr] [, [tier1, tier2, ...]]
2133        let func_path: syn::Path = input.parse()?;
2134
2135        // Parse parenthesized arguments
2136        let content;
2137        syn::parenthesized!(content in input);
2138        let args = content
2139            .parse_terminated(syn::Expr::parse, Token![,])?
2140            .into_iter()
2141            .collect();
2142
2143        // Check for optional "with token"
2144        let with_token = if input.peek(Ident) {
2145            let kw: Ident = input.parse()?;
2146            if kw != "with" {
2147                return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
2148            }
2149            Some(input.parse()?)
2150        } else {
2151            None
2152        };
2153
2154        // Check for optional tier list: , [tier1, tier2, ...]
2155        let tiers = if input.peek(Token![,]) {
2156            let _: Token![,] = input.parse()?;
2157            let bracket_content;
2158            let bracket = syn::bracketed!(bracket_content in input);
2159            let tier_idents = bracket_content.parse_terminated(Ident::parse, Token![,])?;
2160            let tier_names: Vec<String> = tier_idents.iter().map(|i| i.to_string()).collect();
2161            Some((tier_names, bracket.span.join()))
2162        } else {
2163            None
2164        };
2165
2166        Ok(IncantInput {
2167            func_path,
2168            args,
2169            with_token,
2170            tiers,
2171        })
2172    }
2173}
2174
2175/// Dispatch to platform-specific SIMD variants.
2176///
2177/// # Entry Point Mode (no token yet)
2178///
2179/// Summons tokens and dispatches to the best available variant:
2180///
2181/// ```rust,ignore
2182/// pub fn public_api(data: &[f32]) -> f32 {
2183///     incant!(dot(data))
2184/// }
2185/// ```
2186///
2187/// Expands to runtime feature detection + dispatch to `dot_v3`, `dot_v4`,
2188/// `dot_neon`, `dot_wasm128`, or `dot_scalar`.
2189///
2190/// # Explicit Tiers
2191///
2192/// Specify which tiers to dispatch to:
2193///
2194/// ```rust,ignore
2195/// // Only dispatch to v1, v3, neon, and scalar
2196/// pub fn api(data: &[f32]) -> f32 {
2197///     incant!(process(data), [v1, v3, neon, scalar])
2198/// }
2199/// ```
2200///
2201/// Always include `scalar` in explicit tier lists — `incant!` always
2202/// emits a `fn_scalar()` call as the final fallback, and listing it
2203/// documents this dependency. Currently auto-appended if omitted;
2204/// will become a compile error in v1.0. Unknown tier names cause a
2205/// compile error. Tiers are automatically sorted into correct
2206/// dispatch order (highest priority first).
2207///
2208/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
2209/// `neon_sha3`, `neon_crc`, `wasm128`, `wasm128_relaxed`, `scalar`.
2210///
2211/// # Passthrough Mode (already have token)
2212///
2213/// Uses compile-time dispatch via `IntoConcreteToken`:
2214///
2215/// ```rust,ignore
2216/// #[arcane]
2217/// fn outer(token: X64V3Token, data: &[f32]) -> f32 {
2218///     incant!(inner(data) with token)
2219/// }
2220/// ```
2221///
2222/// Also supports explicit tiers:
2223///
2224/// ```rust,ignore
2225/// fn inner<T: IntoConcreteToken>(token: T, data: &[f32]) -> f32 {
2226///     incant!(process(data) with token, [v3, neon, scalar])
2227/// }
2228/// ```
2229///
2230/// The compiler monomorphizes the dispatch, eliminating non-matching branches.
2231///
2232/// # Variant Naming
2233///
2234/// Functions must have suffixed variants matching the selected tiers:
2235/// - `_v1` for `X64V1Token`
2236/// - `_v2` for `X64V2Token`
2237/// - `_v3` for `X64V3Token`
2238/// - `_v4` for `X64V4Token` (requires `avx512` feature)
2239/// - `_v4x` for `X64V4xToken` (requires `avx512` feature)
2240/// - `_neon` for `NeonToken`
2241/// - `_neon_aes` for `NeonAesToken`
2242/// - `_neon_sha3` for `NeonSha3Token`
2243/// - `_neon_crc` for `NeonCrcToken`
2244/// - `_wasm128` for `Wasm128Token`
2245/// - `_scalar` for `ScalarToken`
2246#[proc_macro]
2247pub fn incant(input: TokenStream) -> TokenStream {
2248    let input = parse_macro_input!(input as IncantInput);
2249    incant_impl(input)
2250}
2251
2252/// Legacy alias for [`incant!`].
2253#[proc_macro]
2254pub fn simd_route(input: TokenStream) -> TokenStream {
2255    let input = parse_macro_input!(input as IncantInput);
2256    incant_impl(input)
2257}
2258
2259/// Descriptive alias for [`incant!`].
2260///
2261/// Dispatches to architecture-specific function variants at runtime.
2262/// Looks for suffixed functions (`_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`)
2263/// and calls the best one the CPU supports.
2264///
2265/// See [`incant!`] for full documentation and examples.
2266#[proc_macro]
2267pub fn dispatch_variant(input: TokenStream) -> TokenStream {
2268    let input = parse_macro_input!(input as IncantInput);
2269    incant_impl(input)
2270}
2271
2272fn incant_impl(input: IncantInput) -> TokenStream {
2273    let func_path = &input.func_path;
2274    let args = &input.args;
2275
2276    // Resolve tiers
2277    let tier_names: Vec<String> = match &input.tiers {
2278        Some((names, _)) => names.clone(),
2279        None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2280    };
2281    let last_segment_span = func_path
2282        .segments
2283        .last()
2284        .map(|s| s.ident.span())
2285        .unwrap_or_else(proc_macro2::Span::call_site);
2286    let error_span = input
2287        .tiers
2288        .as_ref()
2289        .map(|(_, span)| *span)
2290        .unwrap_or(last_segment_span);
2291
2292    // When the user specifies explicit tiers, require `scalar` in the list.
2293    // This forces acknowledgment that a scalar fallback path exists and must
2294    // be implemented. Default tiers (no bracket list) always include scalar.
2295    // TODO(v1.0): flip REQUIRE_EXPLICIT_SCALAR to true
2296    if REQUIRE_EXPLICIT_SCALAR
2297        && let Some((names, span)) = &input.tiers
2298        && !names.iter().any(|n| n == "scalar")
2299    {
2300        return syn::Error::new(
2301            *span,
2302            "explicit tier list must include `scalar`. \
2303             incant! always dispatches to fn_scalar() as the final fallback, \
2304             so `scalar` must appear in the tier list to acknowledge this. \
2305             Example: [v3, neon, scalar]",
2306        )
2307        .to_compile_error()
2308        .into();
2309    }
2310
2311    let tiers = match resolve_tiers(&tier_names, error_span) {
2312        Ok(t) => t,
2313        Err(e) => return e.to_compile_error().into(),
2314    };
2315
2316    // Group tiers by architecture for cfg-guarded blocks
2317    // Within each arch, tiers are already sorted by priority (highest first)
2318    if let Some(token_expr) = &input.with_token {
2319        gen_incant_passthrough(func_path, args, token_expr, &tiers)
2320    } else {
2321        gen_incant_entry(func_path, args, &tiers)
2322    }
2323}
2324
2325/// Generate incant! passthrough mode (already have a token).
2326fn gen_incant_passthrough(
2327    func_path: &syn::Path,
2328    args: &[syn::Expr],
2329    token_expr: &syn::Expr,
2330    tiers: &[&TierDescriptor],
2331) -> TokenStream {
2332    let mut dispatch_arms = Vec::new();
2333
2334    // Group non-scalar tiers by (target_arch, cargo_feature) for nested cfg blocks
2335    let mut arch_groups: Vec<(Option<&str>, Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2336    for tier in tiers {
2337        if tier.name == "scalar" {
2338            continue; // Handle scalar separately at the end
2339        }
2340        let key = (tier.target_arch, tier.cargo_feature);
2341        if let Some(group) = arch_groups.iter_mut().find(|(a, f, _)| (*a, *f) == key) {
2342            group.2.push(tier);
2343        } else {
2344            arch_groups.push((tier.target_arch, tier.cargo_feature, vec![tier]));
2345        }
2346    }
2347
2348    for (target_arch, cargo_feature, group_tiers) in &arch_groups {
2349        let mut tier_checks = Vec::new();
2350        for tier in group_tiers {
2351            let fn_suffixed = suffix_path(func_path, tier.suffix);
2352            let as_method = format_ident!("{}", tier.as_method);
2353            tier_checks.push(quote! {
2354                if let Some(__t) = __incant_token.#as_method() {
2355                    break '__incant #fn_suffixed(__t, #(#args),*);
2356                }
2357            });
2358        }
2359
2360        let inner = quote! { #(#tier_checks)* };
2361
2362        let guarded = match (target_arch, cargo_feature) {
2363            (Some(arch), Some(feat)) => quote! {
2364                #[cfg(target_arch = #arch)]
2365                {
2366                    #[cfg(feature = #feat)]
2367                    { #inner }
2368                }
2369            },
2370            (Some(arch), None) => quote! {
2371                #[cfg(target_arch = #arch)]
2372                { #inner }
2373            },
2374            (None, Some(feat)) => quote! {
2375                #[cfg(feature = #feat)]
2376                { #inner }
2377            },
2378            (None, None) => inner,
2379        };
2380
2381        dispatch_arms.push(guarded);
2382    }
2383
2384    // Scalar fallback (always last)
2385    let fn_scalar = suffix_path(func_path, "scalar");
2386    let scalar_arm = if tiers.iter().any(|t| t.name == "scalar") {
2387        quote! {
2388            if let Some(__t) = __incant_token.as_scalar() {
2389                break '__incant #fn_scalar(__t, #(#args),*);
2390            }
2391            unreachable!("Token did not match any known variant")
2392        }
2393    } else {
2394        quote! { unreachable!("Token did not match any known variant") }
2395    };
2396
2397    let expanded = quote! {
2398        '__incant: {
2399            use archmage::IntoConcreteToken;
2400            let __incant_token = #token_expr;
2401            #(#dispatch_arms)*
2402            #scalar_arm
2403        }
2404    };
2405    expanded.into()
2406}
2407
2408/// Generate incant! entry point mode (summon tokens).
2409fn gen_incant_entry(
2410    func_path: &syn::Path,
2411    args: &[syn::Expr],
2412    tiers: &[&TierDescriptor],
2413) -> TokenStream {
2414    let mut dispatch_arms = Vec::new();
2415
2416    // Group non-scalar tiers by target_arch for cfg blocks.
2417    // Within each arch group, further split by cargo_feature.
2418    let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2419    for tier in tiers {
2420        if tier.name == "scalar" {
2421            continue;
2422        }
2423        if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2424            group.1.push(tier);
2425        } else {
2426            arch_groups.push((tier.target_arch, vec![tier]));
2427        }
2428    }
2429
2430    for (target_arch, group_tiers) in &arch_groups {
2431        let mut tier_checks = Vec::new();
2432        for tier in group_tiers {
2433            let fn_suffixed = suffix_path(func_path, tier.suffix);
2434            let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2435
2436            let check = quote! {
2437                if let Some(__t) = #token_path::summon() {
2438                    break '__incant #fn_suffixed(__t, #(#args),*);
2439                }
2440            };
2441
2442            if let Some(feat) = tier.cargo_feature {
2443                tier_checks.push(quote! {
2444                    #[cfg(feature = #feat)]
2445                    { #check }
2446                });
2447            } else {
2448                tier_checks.push(check);
2449            }
2450        }
2451
2452        let inner = quote! { #(#tier_checks)* };
2453
2454        if let Some(arch) = target_arch {
2455            dispatch_arms.push(quote! {
2456                #[cfg(target_arch = #arch)]
2457                { #inner }
2458            });
2459        } else {
2460            dispatch_arms.push(inner);
2461        }
2462    }
2463
2464    // Scalar fallback
2465    let fn_scalar = suffix_path(func_path, "scalar");
2466
2467    let expanded = quote! {
2468        '__incant: {
2469            use archmage::SimdToken;
2470            #(#dispatch_arms)*
2471            #fn_scalar(archmage::ScalarToken, #(#args),*)
2472        }
2473    };
2474    expanded.into()
2475}
2476
2477// =============================================================================
2478// autoversion - combined variant generation + dispatch
2479// =============================================================================
2480
2481/// Arguments to the `#[autoversion]` macro.
2482struct AutoversionArgs {
2483    /// The concrete type to use for `self` receiver (inherent methods only).
2484    self_type: Option<Type>,
2485    /// Explicit tier names (None = default tiers).
2486    tiers: Option<Vec<String>>,
2487}
2488
2489impl Parse for AutoversionArgs {
2490    fn parse(input: ParseStream) -> syn::Result<Self> {
2491        let mut self_type = None;
2492        let mut tier_names = Vec::new();
2493
2494        while !input.is_empty() {
2495            let ident: Ident = input.parse()?;
2496            if ident == "_self" {
2497                let _: Token![=] = input.parse()?;
2498                self_type = Some(input.parse()?);
2499            } else {
2500                // Treat as tier name — validated later by resolve_tiers
2501                tier_names.push(ident.to_string());
2502            }
2503            if input.peek(Token![,]) {
2504                let _: Token![,] = input.parse()?;
2505            }
2506        }
2507
2508        Ok(AutoversionArgs {
2509            self_type,
2510            tiers: if tier_names.is_empty() {
2511                None
2512            } else {
2513                Some(tier_names)
2514            },
2515        })
2516    }
2517}
2518
2519/// Information about the `SimdToken` parameter found in a function signature.
2520struct SimdTokenParamInfo {
2521    /// Index of the parameter in `sig.inputs`
2522    index: usize,
2523    /// The parameter identifier
2524    #[allow(dead_code)]
2525    ident: Ident,
2526}
2527
2528/// Find the `SimdToken` parameter in a function signature.
2529///
2530/// Searches all typed parameters for one whose type path ends in `SimdToken`.
2531/// Returns the parameter index and identifier, or `None` if not found.
2532fn find_simd_token_param(sig: &Signature) -> Option<SimdTokenParamInfo> {
2533    for (i, arg) in sig.inputs.iter().enumerate() {
2534        if let FnArg::Typed(PatType { pat, ty, .. }) = arg
2535            && let Type::Path(type_path) = ty.as_ref()
2536            && let Some(seg) = type_path.path.segments.last()
2537            && seg.ident == "SimdToken"
2538        {
2539            let ident = match pat.as_ref() {
2540                syn::Pat::Ident(pi) => pi.ident.clone(),
2541                syn::Pat::Wild(w) => Ident::new("__autoversion_token", w.underscore_token.span),
2542                _ => continue,
2543            };
2544            return Some(SimdTokenParamInfo { index: i, ident });
2545        }
2546    }
2547    None
2548}
2549
2550/// Core implementation for `#[autoversion]`.
2551///
2552/// Generates suffixed SIMD variants (like `#[magetypes]`) and a runtime
2553/// dispatcher function (like `incant!`) from a single annotated function.
2554fn autoversion_impl(mut input_fn: LightFn, args: AutoversionArgs) -> TokenStream {
2555    // Check for self receiver
2556    let has_self = input_fn
2557        .sig
2558        .inputs
2559        .first()
2560        .is_some_and(|arg| matches!(arg, FnArg::Receiver(_)));
2561
2562    // _self = Type is only needed for trait impls (nested mode in #[arcane]).
2563    // For inherent methods, self/Self work naturally in sibling mode.
2564
2565    // Find SimdToken parameter
2566    let token_param = match find_simd_token_param(&input_fn.sig) {
2567        Some(p) => p,
2568        None => {
2569            return syn::Error::new_spanned(
2570                &input_fn.sig,
2571                "autoversion requires a `SimdToken` parameter.\n\
2572                 Example: fn process(token: SimdToken, data: &[f32]) -> f32 { ... }\n\n\
2573                 SimdToken is the dispatch placeholder — autoversion replaces it \
2574                 with concrete token types and generates a runtime dispatcher.",
2575            )
2576            .to_compile_error()
2577            .into();
2578        }
2579    };
2580
2581    // Resolve tiers
2582    let tier_names: Vec<String> = match &args.tiers {
2583        Some(names) => names.clone(),
2584        None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2585    };
2586    let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
2587        Ok(t) => t,
2588        Err(e) => return e.to_compile_error().into(),
2589    };
2590
2591    // Strip #[arcane] / #[rite] to prevent double-wrapping
2592    input_fn
2593        .attrs
2594        .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
2595
2596    let fn_name = &input_fn.sig.ident;
2597    let vis = input_fn.vis.clone();
2598
2599    // Move attrs to dispatcher only; variants get no user attrs
2600    let fn_attrs: Vec<Attribute> = input_fn.attrs.drain(..).collect();
2601
2602    // =========================================================================
2603    // Generate suffixed variants
2604    // =========================================================================
2605    //
2606    // AST manipulation only — we clone the parsed LightFn and swap the token
2607    // param's type annotation. No serialize/reparse round-trip. The body is
2608    // never touched unless _self = Type requires a `let _self = self;`
2609    // preamble on the scalar variant.
2610
2611    let mut variants = Vec::new();
2612
2613    for tier in &tiers {
2614        let mut variant_fn = input_fn.clone();
2615
2616        // Variants are always private — only the dispatcher is public.
2617        variant_fn.vis = syn::Visibility::Inherited;
2618
2619        // Rename: process → process_v3
2620        variant_fn.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
2621
2622        // Replace SimdToken param type with concrete token type
2623        let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
2624        if let FnArg::Typed(pt) = &mut variant_fn.sig.inputs[token_param.index] {
2625            *pt.ty = concrete_type;
2626        }
2627
2628        // Scalar with _self = Type: inject `let _self = self;` preamble so body's
2629        // _self references resolve (non-scalar variants get this from #[arcane(_self = Type)])
2630        if tier.name == "scalar" && has_self && args.self_type.is_some() {
2631            let original_body = variant_fn.body.clone();
2632            variant_fn.body = quote!(let _self = self; #original_body);
2633        }
2634
2635        // cfg guard
2636        let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
2637            (Some(arch), Some(feature)) => {
2638                quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
2639            }
2640            (Some(arch), None) => quote! { #[cfg(target_arch = #arch)] },
2641            (None, Some(feature)) => quote! { #[cfg(feature = #feature)] },
2642            (None, None) => quote! {},
2643        };
2644
2645        if tier.name != "scalar" {
2646            // Non-scalar: add #[arcane] (with _self if needed)
2647            let arcane_attr = if let Some(ref self_type) = args.self_type {
2648                quote! { #[archmage::arcane(_self = #self_type)] }
2649            } else {
2650                quote! { #[archmage::arcane] }
2651            };
2652            variants.push(quote! {
2653                #cfg_guard
2654                #arcane_attr
2655                #variant_fn
2656            });
2657        } else {
2658            variants.push(quote! {
2659                #cfg_guard
2660                #variant_fn
2661            });
2662        }
2663    }
2664
2665    // =========================================================================
2666    // Generate dispatcher (adapted from gen_incant_entry)
2667    // =========================================================================
2668
2669    // Build dispatcher inputs: original params minus SimdToken
2670    let mut dispatcher_inputs: Vec<FnArg> = input_fn.sig.inputs.iter().cloned().collect();
2671    dispatcher_inputs.remove(token_param.index);
2672
2673    // Rename wildcard params so we can pass them as arguments
2674    let mut wild_counter = 0u32;
2675    for arg in &mut dispatcher_inputs {
2676        if let FnArg::Typed(pat_type) = arg
2677            && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
2678        {
2679            let ident = format_ident!("__autoversion_wild_{}", wild_counter);
2680            wild_counter += 1;
2681            *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
2682                attrs: vec![],
2683                by_ref: None,
2684                mutability: None,
2685                ident,
2686                subpat: None,
2687            });
2688        }
2689    }
2690
2691    // Collect argument idents for dispatch calls (exclude self receiver)
2692    let dispatch_args: Vec<Ident> = dispatcher_inputs
2693        .iter()
2694        .filter_map(|arg| {
2695            if let FnArg::Typed(PatType { pat, .. }) = arg
2696                && let syn::Pat::Ident(pi) = pat.as_ref()
2697            {
2698                return Some(pi.ident.clone());
2699            }
2700            None
2701        })
2702        .collect();
2703
2704    // Build turbofish for forwarding type/const generics to variant calls
2705    let turbofish = build_turbofish(&input_fn.sig.generics);
2706
2707    // Group non-scalar tiers by target_arch for cfg blocks
2708    let mut arch_groups: Vec<(Option<&str>, Vec<&&TierDescriptor>)> = Vec::new();
2709    for tier in &tiers {
2710        if tier.name == "scalar" {
2711            continue;
2712        }
2713        if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2714            group.1.push(tier);
2715        } else {
2716            arch_groups.push((tier.target_arch, vec![tier]));
2717        }
2718    }
2719
2720    let mut dispatch_arms = Vec::new();
2721    for (target_arch, group_tiers) in &arch_groups {
2722        let mut tier_checks = Vec::new();
2723        for tier in group_tiers {
2724            let suffixed = format_ident!("{}_{}", fn_name, tier.suffix);
2725            let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2726
2727            let call = if has_self {
2728                quote! { self.#suffixed #turbofish(__t, #(#dispatch_args),*) }
2729            } else {
2730                quote! { #suffixed #turbofish(__t, #(#dispatch_args),*) }
2731            };
2732
2733            let check = quote! {
2734                if let Some(__t) = #token_path::summon() {
2735                    break '__dispatch #call;
2736                }
2737            };
2738
2739            if let Some(feat) = tier.cargo_feature {
2740                tier_checks.push(quote! {
2741                    #[cfg(feature = #feat)]
2742                    { #check }
2743                });
2744            } else {
2745                tier_checks.push(check);
2746            }
2747        }
2748
2749        let inner = quote! { #(#tier_checks)* };
2750
2751        if let Some(arch) = target_arch {
2752            dispatch_arms.push(quote! {
2753                #[cfg(target_arch = #arch)]
2754                { #inner }
2755            });
2756        } else {
2757            dispatch_arms.push(inner);
2758        }
2759    }
2760
2761    // Scalar fallback (always available, no summon needed)
2762    let scalar_name = format_ident!("{}_scalar", fn_name);
2763    let scalar_call = if has_self {
2764        quote! { self.#scalar_name #turbofish(archmage::ScalarToken, #(#dispatch_args),*) }
2765    } else {
2766        quote! { #scalar_name #turbofish(archmage::ScalarToken, #(#dispatch_args),*) }
2767    };
2768
2769    // Build dispatcher function
2770    let dispatcher_inputs_punct: syn::punctuated::Punctuated<FnArg, Token![,]> =
2771        dispatcher_inputs.into_iter().collect();
2772    let output = &input_fn.sig.output;
2773    let generics = &input_fn.sig.generics;
2774    let where_clause = &generics.where_clause;
2775
2776    let dispatcher = quote! {
2777        #(#fn_attrs)*
2778        #vis fn #fn_name #generics (#dispatcher_inputs_punct) #output #where_clause {
2779            '__dispatch: {
2780                use archmage::SimdToken;
2781                #(#dispatch_arms)*
2782                #scalar_call
2783            }
2784        }
2785    };
2786
2787    let expanded = quote! {
2788        #dispatcher
2789        #(#variants)*
2790    };
2791
2792    expanded.into()
2793}
2794
2795/// Let the compiler auto-vectorize scalar code for each architecture.
2796///
2797/// Write a plain scalar function with a `SimdToken` placeholder parameter.
2798/// `#[autoversion]` generates architecture-specific copies — each compiled
2799/// with different `#[target_feature]` flags via `#[arcane]` — plus a runtime
2800/// dispatcher that calls the best one the CPU supports.
2801///
2802/// You don't touch intrinsics, don't import SIMD types, don't think about
2803/// lane widths. The compiler's auto-vectorizer does the work; you give it
2804/// permission via `#[target_feature]`, which `#[autoversion]` handles.
2805///
2806/// # The simple win
2807///
2808/// ```rust,ignore
2809/// use archmage::SimdToken;
2810///
2811/// #[autoversion]
2812/// fn sum_of_squares(_token: SimdToken, data: &[f32]) -> f32 {
2813///     let mut sum = 0.0f32;
2814///     for &x in data {
2815///         sum += x * x;
2816///     }
2817///     sum
2818/// }
2819///
2820/// // Call directly — no token, no unsafe:
2821/// let result = sum_of_squares(&my_data);
2822/// ```
2823///
2824/// The `_token` parameter is never used in the body. It exists so the macro
2825/// knows where to substitute concrete token types. Each generated variant
2826/// gets `#[arcane]` → `#[target_feature(enable = "avx2,fma,...")]`, which
2827/// unlocks the compiler's auto-vectorizer for that feature set.
2828///
2829/// On x86-64 with the `_v3` variant (AVX2+FMA), that loop compiles to
2830/// `vfmadd231ps` — fused multiply-add on 8 floats per cycle. On aarch64
2831/// with NEON, you get `fmla`. The `_scalar` fallback compiles without any
2832/// SIMD target features, as a safety net for unknown hardware.
2833///
2834/// # Chunks + remainder
2835///
2836/// The classic data-processing pattern works naturally:
2837///
2838/// ```rust,ignore
2839/// #[autoversion]
2840/// fn normalize(_token: SimdToken, data: &mut [f32], scale: f32) {
2841///     // Compiler auto-vectorizes this — no manual SIMD needed.
2842///     // On v3, this becomes vdivps + vmulps on 8 floats at a time.
2843///     for x in data.iter_mut() {
2844///         *x = (*x - 128.0) * scale;
2845///     }
2846/// }
2847/// ```
2848///
2849/// If you want explicit control over chunk boundaries (e.g., for
2850/// accumulator patterns), that works too:
2851///
2852/// ```rust,ignore
2853/// #[autoversion]
2854/// fn dot_product(_token: SimdToken, a: &[f32], b: &[f32]) -> f32 {
2855///     let n = a.len().min(b.len());
2856///     let mut sum = 0.0f32;
2857///     for i in 0..n {
2858///         sum += a[i] * b[i];
2859///     }
2860///     sum
2861/// }
2862/// ```
2863///
2864/// The compiler decides the chunk size based on the target features of each
2865/// variant (8 floats for AVX2, 4 for NEON, 1 for scalar).
2866///
2867/// # What gets generated
2868///
2869/// With default tiers, `#[autoversion] fn process(_t: SimdToken, data: &[f32]) -> f32`
2870/// expands to:
2871///
2872/// - `process_v4(token: X64V4Token, ...)` — AVX-512 (behind `#[cfg(feature = "avx512")]`)
2873/// - `process_v3(token: X64V3Token, ...)` — AVX2+FMA
2874/// - `process_neon(token: NeonToken, ...)` — aarch64 NEON
2875/// - `process_wasm128(token: Wasm128Token, ...)` — WASM SIMD
2876/// - `process_scalar(token: ScalarToken, ...)` — no SIMD, always available
2877/// - `process(data: &[f32]) -> f32` — **dispatcher** (SimdToken param removed)
2878///
2879/// Each non-scalar variant is wrapped in `#[arcane]` (for `#[target_feature]`)
2880/// and `#[cfg(target_arch = ...)]`. The dispatcher does runtime CPU feature
2881/// detection via `Token::summon()` and calls the best match. When compiled
2882/// with `-C target-cpu=native`, the detection is elided by the compiler.
2883///
2884/// The suffixed variants are private sibling functions — only the dispatcher
2885/// is public. Within the same module, you can call them directly for testing
2886/// or benchmarking.
2887///
2888/// # SimdToken replacement
2889///
2890/// `#[autoversion]` replaces the `SimdToken` type annotation in the function
2891/// signature with the concrete token type for each variant (e.g.,
2892/// `archmage::X64V3Token`). Only the parameter's type changes — the function
2893/// body is never reparsed, which keeps compile times low.
2894///
2895/// The token variable (whatever you named it — `token`, `_token`, `_t`)
2896/// keeps working in the body because its type comes from the signature.
2897/// So `f32x8::from_array(token, ...)` works — `token` is now an `X64V3Token`
2898/// which satisfies the same trait bounds as `SimdToken`.
2899///
2900/// `#[magetypes]` takes a different approach: it replaces the text `Token`
2901/// everywhere in the function — signature and body — via string substitution.
2902/// Use `#[magetypes]` when you need body-level type substitution (e.g.,
2903/// `Token`-dependent constants or type aliases that differ per variant).
2904/// Use `#[autoversion]` when you want compiler auto-vectorization of scalar
2905/// code with zero boilerplate.
2906///
2907/// # Benchmarking
2908///
2909/// Measure the speedup with a side-by-side comparison. The generated
2910/// `_scalar` variant serves as the baseline; the dispatcher picks the
2911/// best available:
2912///
2913/// ```rust,ignore
2914/// use criterion::{Criterion, black_box, criterion_group, criterion_main};
2915/// use archmage::SimdToken;
2916///
2917/// #[autoversion]
2918/// fn sum_squares(_token: SimdToken, data: &[f32]) -> f32 {
2919///     data.iter().map(|&x| x * x).fold(0.0f32, |a, b| a + b)
2920/// }
2921///
2922/// fn bench(c: &mut Criterion) {
2923///     let data: Vec<f32> = (0..4096).map(|i| i as f32 * 0.01).collect();
2924///     let mut group = c.benchmark_group("sum_squares");
2925///
2926///     // Dispatched — picks best available at runtime
2927///     group.bench_function("dispatched", |b| {
2928///         b.iter(|| sum_squares(black_box(&data)))
2929///     });
2930///
2931///     // Scalar baseline — no target_feature, no auto-vectorization
2932///     group.bench_function("scalar", |b| {
2933///         b.iter(|| sum_squares_scalar(archmage::ScalarToken, black_box(&data)))
2934///     });
2935///
2936///     // Specific tier (useful for isolating which tier wins)
2937///     #[cfg(target_arch = "x86_64")]
2938///     if let Some(t) = archmage::X64V3Token::summon() {
2939///         group.bench_function("v3_avx2_fma", |b| {
2940///             b.iter(|| sum_squares_v3(t, black_box(&data)));
2941///         });
2942///     }
2943///
2944///     group.finish();
2945/// }
2946///
2947/// criterion_group!(benches, bench);
2948/// criterion_main!(benches);
2949/// ```
2950///
2951/// For a tight numeric loop on x86-64, the `_v3` variant (AVX2+FMA)
2952/// typically runs 4-8x faster than `_scalar` because `#[target_feature]`
2953/// unlocks auto-vectorization that the baseline build can't use.
2954///
2955/// # Explicit tiers
2956///
2957/// ```rust,ignore
2958/// #[autoversion(v3, v4, v4x, neon, arm_v2, wasm128)]
2959/// fn process(_token: SimdToken, data: &[f32]) -> f32 {
2960///     // ...
2961/// }
2962/// ```
2963///
2964/// `scalar` is always included implicitly.
2965///
2966/// Default tiers (when no list given): `v4`, `v3`, `neon`, `wasm128`, `scalar`.
2967///
2968/// Known tiers: `v1`, `v2`, `v3`, `v3_crypto`, `v4`, `v4x`, `neon`,
2969/// `neon_aes`, `neon_sha3`, `neon_crc`, `arm_v2`, `arm_v3`, `wasm128`,
2970/// `wasm128_relaxed`, `x64_crypto`, `scalar`.
2971///
2972/// # Methods with self receivers
2973///
2974/// For inherent methods, `self` works naturally — no `_self` needed:
2975///
2976/// ```rust,ignore
2977/// impl ImageBuffer {
2978///     #[autoversion]
2979///     fn normalize(&mut self, token: SimdToken, gamma: f32) {
2980///         for pixel in &mut self.data {
2981///             *pixel = (*pixel / 255.0).powf(gamma);
2982///         }
2983///     }
2984/// }
2985///
2986/// // Call normally — no token:
2987/// buffer.normalize(2.2);
2988/// ```
2989///
2990/// All receiver types work: `self`, `&self`, `&mut self`. Non-scalar variants
2991/// get `#[arcane]` (sibling mode), where `self`/`Self` resolve naturally.
2992///
2993/// # Trait methods (requires `_self = Type`)
2994///
2995/// Trait methods can't use `#[autoversion]` directly because proc macro
2996/// attributes on trait impl items can't expand to multiple sibling functions.
2997/// Use the delegation pattern with `_self = Type`:
2998///
2999/// ```rust,ignore
3000/// trait Processor {
3001///     fn process(&self, data: &[f32]) -> f32;
3002/// }
3003///
3004/// impl Processor for MyType {
3005///     fn process(&self, data: &[f32]) -> f32 {
3006///         self.process_impl(data) // delegate to autoversioned method
3007///     }
3008/// }
3009///
3010/// impl MyType {
3011///     #[autoversion(_self = MyType)]
3012///     fn process_impl(&self, token: SimdToken, data: &[f32]) -> f32 {
3013///         _self.weights.iter().zip(data).map(|(w, d)| w * d).sum()
3014///     }
3015/// }
3016/// ```
3017///
3018/// `_self = Type` uses nested mode in `#[arcane]`, which is required for
3019/// trait impls. Use `_self` (not `self`) in the body when using this form.
3020///
3021/// # Comparison with `#[magetypes]` + `incant!`
3022///
3023/// | | `#[autoversion]` | `#[magetypes]` + `incant!` |
3024/// |---|---|---|
3025/// | Placeholder | `SimdToken` | `Token` |
3026/// | Generates variants | Yes | Yes (magetypes) |
3027/// | Generates dispatcher | Yes | No (you write `incant!`) |
3028/// | Best for | Scalar auto-vectorization | Explicit SIMD with typed vectors |
3029/// | Lines of code | 1 attribute | 2+ (magetypes + incant + arcane) |
3030///
3031/// Use `#[autoversion]` for scalar loops you want auto-vectorized. Use
3032/// `#[magetypes]` + `incant!` when you need `f32x8`, `u8x32`, and
3033/// hand-tuned SIMD code per architecture
3034#[proc_macro_attribute]
3035pub fn autoversion(attr: TokenStream, item: TokenStream) -> TokenStream {
3036    let args = parse_macro_input!(attr as AutoversionArgs);
3037    let input_fn = parse_macro_input!(item as LightFn);
3038    autoversion_impl(input_fn, args)
3039}
3040
3041// =============================================================================
3042// Unit tests for token/trait recognition maps
3043// =============================================================================
3044
3045#[cfg(test)]
3046mod tests {
3047    use super::*;
3048
3049    use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
3050    use syn::{ItemFn, ReturnType};
3051
3052    #[test]
3053    fn every_concrete_token_is_in_token_to_features() {
3054        for &name in ALL_CONCRETE_TOKENS {
3055            assert!(
3056                token_to_features(name).is_some(),
3057                "Token `{}` exists in runtime crate but is NOT recognized by \
3058                 token_to_features() in the proc macro. Add it!",
3059                name
3060            );
3061        }
3062    }
3063
3064    #[test]
3065    fn every_trait_is_in_trait_to_features() {
3066        for &name in ALL_TRAIT_NAMES {
3067            assert!(
3068                trait_to_features(name).is_some(),
3069                "Trait `{}` exists in runtime crate but is NOT recognized by \
3070                 trait_to_features() in the proc macro. Add it!",
3071                name
3072            );
3073        }
3074    }
3075
3076    #[test]
3077    fn token_aliases_map_to_same_features() {
3078        // Desktop64 = X64V3Token
3079        assert_eq!(
3080            token_to_features("Desktop64"),
3081            token_to_features("X64V3Token"),
3082            "Desktop64 and X64V3Token should map to identical features"
3083        );
3084
3085        // Server64 = X64V4Token = Avx512Token
3086        assert_eq!(
3087            token_to_features("Server64"),
3088            token_to_features("X64V4Token"),
3089            "Server64 and X64V4Token should map to identical features"
3090        );
3091        assert_eq!(
3092            token_to_features("X64V4Token"),
3093            token_to_features("Avx512Token"),
3094            "X64V4Token and Avx512Token should map to identical features"
3095        );
3096
3097        // Arm64 = NeonToken
3098        assert_eq!(
3099            token_to_features("Arm64"),
3100            token_to_features("NeonToken"),
3101            "Arm64 and NeonToken should map to identical features"
3102        );
3103    }
3104
3105    #[test]
3106    fn trait_to_features_includes_tokens_as_bounds() {
3107        // Tier tokens should also work as trait bounds
3108        // (for `impl X64V3Token` patterns, even though Rust won't allow it,
3109        // the macro processes AST before type checking)
3110        let tier_tokens = [
3111            "X64V2Token",
3112            "X64CryptoToken",
3113            "X64V3Token",
3114            "Desktop64",
3115            "Avx2FmaToken",
3116            "X64V4Token",
3117            "Avx512Token",
3118            "Server64",
3119            "X64V4xToken",
3120            "Avx512Fp16Token",
3121            "NeonToken",
3122            "Arm64",
3123            "NeonAesToken",
3124            "NeonSha3Token",
3125            "NeonCrcToken",
3126            "Arm64V2Token",
3127            "Arm64V3Token",
3128        ];
3129
3130        for &name in &tier_tokens {
3131            assert!(
3132                trait_to_features(name).is_some(),
3133                "Tier token `{}` should also be recognized in trait_to_features() \
3134                 for use as a generic bound. Add it!",
3135                name
3136            );
3137        }
3138    }
3139
3140    #[test]
3141    fn trait_features_are_cumulative() {
3142        // HasX64V4 should include all HasX64V2 features plus more
3143        let v2_features = trait_to_features("HasX64V2").unwrap();
3144        let v4_features = trait_to_features("HasX64V4").unwrap();
3145
3146        for &f in v2_features {
3147            assert!(
3148                v4_features.contains(&f),
3149                "HasX64V4 should include v2 feature `{}` but doesn't",
3150                f
3151            );
3152        }
3153
3154        // v4 should have more features than v2
3155        assert!(
3156            v4_features.len() > v2_features.len(),
3157            "HasX64V4 should have more features than HasX64V2"
3158        );
3159    }
3160
3161    #[test]
3162    fn x64v3_trait_features_include_v2() {
3163        // X64V3Token as trait bound should include v2 features
3164        let v2 = trait_to_features("HasX64V2").unwrap();
3165        let v3 = trait_to_features("X64V3Token").unwrap();
3166
3167        for &f in v2 {
3168            assert!(
3169                v3.contains(&f),
3170                "X64V3Token trait features should include v2 feature `{}` but don't",
3171                f
3172            );
3173        }
3174    }
3175
3176    #[test]
3177    fn has_neon_aes_includes_neon() {
3178        let neon = trait_to_features("HasNeon").unwrap();
3179        let neon_aes = trait_to_features("HasNeonAes").unwrap();
3180
3181        for &f in neon {
3182            assert!(
3183                neon_aes.contains(&f),
3184                "HasNeonAes should include NEON feature `{}`",
3185                f
3186            );
3187        }
3188    }
3189
3190    #[test]
3191    fn no_removed_traits_are_recognized() {
3192        // These traits were removed in 0.3.0 and should NOT be recognized
3193        let removed = [
3194            "HasSse",
3195            "HasSse2",
3196            "HasSse41",
3197            "HasSse42",
3198            "HasAvx",
3199            "HasAvx2",
3200            "HasFma",
3201            "HasAvx512f",
3202            "HasAvx512bw",
3203            "HasAvx512vl",
3204            "HasAvx512vbmi2",
3205            "HasSve",
3206            "HasSve2",
3207        ];
3208
3209        for &name in &removed {
3210            assert!(
3211                trait_to_features(name).is_none(),
3212                "Removed trait `{}` should NOT be in trait_to_features(). \
3213                 It was removed in 0.3.0 — users should migrate to tier traits.",
3214                name
3215            );
3216        }
3217    }
3218
3219    #[test]
3220    fn no_nonexistent_tokens_are_recognized() {
3221        // These tokens don't exist and should NOT be recognized
3222        let fake = [
3223            "SveToken",
3224            "Sve2Token",
3225            "Avx512VnniToken",
3226            "X64V4ModernToken",
3227            "NeonFp16Token",
3228        ];
3229
3230        for &name in &fake {
3231            assert!(
3232                token_to_features(name).is_none(),
3233                "Non-existent token `{}` should NOT be in token_to_features()",
3234                name
3235            );
3236        }
3237    }
3238
3239    #[test]
3240    fn featureless_traits_are_not_in_registries() {
3241        // SimdToken and IntoConcreteToken should NOT be in any feature registry
3242        // because they don't map to CPU features
3243        for &name in FEATURELESS_TRAIT_NAMES {
3244            assert!(
3245                token_to_features(name).is_none(),
3246                "`{}` should NOT be in token_to_features() — it has no CPU features",
3247                name
3248            );
3249            assert!(
3250                trait_to_features(name).is_none(),
3251                "`{}` should NOT be in trait_to_features() — it has no CPU features",
3252                name
3253            );
3254        }
3255    }
3256
3257    #[test]
3258    fn find_featureless_trait_detects_simdtoken() {
3259        let names = vec!["SimdToken".to_string()];
3260        assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3261
3262        let names = vec!["IntoConcreteToken".to_string()];
3263        assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
3264
3265        // Feature-bearing traits should NOT be detected
3266        let names = vec!["HasX64V2".to_string()];
3267        assert_eq!(find_featureless_trait(&names), None);
3268
3269        let names = vec!["HasNeon".to_string()];
3270        assert_eq!(find_featureless_trait(&names), None);
3271
3272        // Mixed: if SimdToken is among real traits, still detected
3273        let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
3274        assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3275    }
3276
3277    #[test]
3278    fn arm64_v2_v3_traits_are_cumulative() {
3279        let v2_features = trait_to_features("HasArm64V2").unwrap();
3280        let v3_features = trait_to_features("HasArm64V3").unwrap();
3281
3282        for &f in v2_features {
3283            assert!(
3284                v3_features.contains(&f),
3285                "HasArm64V3 should include v2 feature `{}` but doesn't",
3286                f
3287            );
3288        }
3289
3290        assert!(
3291            v3_features.len() > v2_features.len(),
3292            "HasArm64V3 should have more features than HasArm64V2"
3293        );
3294    }
3295
3296    // =========================================================================
3297    // autoversion — argument parsing
3298    // =========================================================================
3299
3300    #[test]
3301    fn autoversion_args_empty() {
3302        let args: AutoversionArgs = syn::parse_str("").unwrap();
3303        assert!(args.self_type.is_none());
3304        assert!(args.tiers.is_none());
3305    }
3306
3307    #[test]
3308    fn autoversion_args_single_tier() {
3309        let args: AutoversionArgs = syn::parse_str("v3").unwrap();
3310        assert!(args.self_type.is_none());
3311        assert_eq!(args.tiers.as_ref().unwrap(), &["v3"]);
3312    }
3313
3314    #[test]
3315    fn autoversion_args_tiers_only() {
3316        let args: AutoversionArgs = syn::parse_str("v3, v4, neon").unwrap();
3317        assert!(args.self_type.is_none());
3318        let tiers = args.tiers.unwrap();
3319        assert_eq!(tiers, vec!["v3", "v4", "neon"]);
3320    }
3321
3322    #[test]
3323    fn autoversion_args_many_tiers() {
3324        let args: AutoversionArgs =
3325            syn::parse_str("v1, v2, v3, v4, v4x, neon, arm_v2, wasm128").unwrap();
3326        assert_eq!(
3327            args.tiers.unwrap(),
3328            vec!["v1", "v2", "v3", "v4", "v4x", "neon", "arm_v2", "wasm128"]
3329        );
3330    }
3331
3332    #[test]
3333    fn autoversion_args_trailing_comma() {
3334        let args: AutoversionArgs = syn::parse_str("v3, v4,").unwrap();
3335        assert_eq!(args.tiers.as_ref().unwrap(), &["v3", "v4"]);
3336    }
3337
3338    #[test]
3339    fn autoversion_args_self_only() {
3340        let args: AutoversionArgs = syn::parse_str("_self = MyType").unwrap();
3341        assert!(args.self_type.is_some());
3342        assert!(args.tiers.is_none());
3343    }
3344
3345    #[test]
3346    fn autoversion_args_self_and_tiers() {
3347        let args: AutoversionArgs = syn::parse_str("_self = MyType, v3, neon").unwrap();
3348        assert!(args.self_type.is_some());
3349        let tiers = args.tiers.unwrap();
3350        assert_eq!(tiers, vec!["v3", "neon"]);
3351    }
3352
3353    #[test]
3354    fn autoversion_args_tiers_then_self() {
3355        // _self can appear after tier names
3356        let args: AutoversionArgs = syn::parse_str("v3, neon, _self = MyType").unwrap();
3357        assert!(args.self_type.is_some());
3358        let tiers = args.tiers.unwrap();
3359        assert_eq!(tiers, vec!["v3", "neon"]);
3360    }
3361
3362    #[test]
3363    fn autoversion_args_self_with_path_type() {
3364        let args: AutoversionArgs = syn::parse_str("_self = crate::MyType").unwrap();
3365        assert!(args.self_type.is_some());
3366        assert!(args.tiers.is_none());
3367    }
3368
3369    #[test]
3370    fn autoversion_args_self_with_generic_type() {
3371        let args: AutoversionArgs = syn::parse_str("_self = Vec<u8>").unwrap();
3372        assert!(args.self_type.is_some());
3373        let ty_str = args.self_type.unwrap().to_token_stream().to_string();
3374        assert!(ty_str.contains("Vec"), "Expected Vec<u8>, got: {}", ty_str);
3375    }
3376
3377    #[test]
3378    fn autoversion_args_self_trailing_comma() {
3379        let args: AutoversionArgs = syn::parse_str("_self = MyType,").unwrap();
3380        assert!(args.self_type.is_some());
3381        assert!(args.tiers.is_none());
3382    }
3383
3384    // =========================================================================
3385    // autoversion — find_simd_token_param
3386    // =========================================================================
3387
3388    #[test]
3389    fn find_simd_token_param_first_position() {
3390        let f: ItemFn =
3391            syn::parse_str("fn process(token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3392        let param = find_simd_token_param(&f.sig).unwrap();
3393        assert_eq!(param.index, 0);
3394        assert_eq!(param.ident, "token");
3395    }
3396
3397    #[test]
3398    fn find_simd_token_param_second_position() {
3399        let f: ItemFn =
3400            syn::parse_str("fn process(data: &[f32], token: SimdToken) -> f32 {}").unwrap();
3401        let param = find_simd_token_param(&f.sig).unwrap();
3402        assert_eq!(param.index, 1);
3403        assert_eq!(param.ident, "token");
3404    }
3405
3406    #[test]
3407    fn find_simd_token_param_underscore_prefix() {
3408        let f: ItemFn =
3409            syn::parse_str("fn process(_token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3410        let param = find_simd_token_param(&f.sig).unwrap();
3411        assert_eq!(param.index, 0);
3412        assert_eq!(param.ident, "_token");
3413    }
3414
3415    #[test]
3416    fn find_simd_token_param_wildcard() {
3417        let f: ItemFn = syn::parse_str("fn process(_: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3418        let param = find_simd_token_param(&f.sig).unwrap();
3419        assert_eq!(param.index, 0);
3420        assert_eq!(param.ident, "__autoversion_token");
3421    }
3422
3423    #[test]
3424    fn find_simd_token_param_not_found() {
3425        let f: ItemFn = syn::parse_str("fn process(data: &[f32]) -> f32 {}").unwrap();
3426        assert!(find_simd_token_param(&f.sig).is_none());
3427    }
3428
3429    #[test]
3430    fn find_simd_token_param_no_params() {
3431        let f: ItemFn = syn::parse_str("fn process() {}").unwrap();
3432        assert!(find_simd_token_param(&f.sig).is_none());
3433    }
3434
3435    #[test]
3436    fn find_simd_token_param_concrete_token_not_matched() {
3437        // autoversion looks specifically for SimdToken, not concrete tokens
3438        let f: ItemFn =
3439            syn::parse_str("fn process(token: X64V3Token, data: &[f32]) -> f32 {}").unwrap();
3440        assert!(find_simd_token_param(&f.sig).is_none());
3441    }
3442
3443    #[test]
3444    fn find_simd_token_param_scalar_token_not_matched() {
3445        let f: ItemFn =
3446            syn::parse_str("fn process(token: ScalarToken, data: &[f32]) -> f32 {}").unwrap();
3447        assert!(find_simd_token_param(&f.sig).is_none());
3448    }
3449
3450    #[test]
3451    fn find_simd_token_param_among_many() {
3452        let f: ItemFn = syn::parse_str(
3453            "fn process(a: i32, b: f64, token: SimdToken, c: &str, d: bool) -> f32 {}",
3454        )
3455        .unwrap();
3456        let param = find_simd_token_param(&f.sig).unwrap();
3457        assert_eq!(param.index, 2);
3458        assert_eq!(param.ident, "token");
3459    }
3460
3461    #[test]
3462    fn find_simd_token_param_with_generics() {
3463        let f: ItemFn =
3464            syn::parse_str("fn process<T: Clone>(token: SimdToken, data: &[T]) -> T {}").unwrap();
3465        let param = find_simd_token_param(&f.sig).unwrap();
3466        assert_eq!(param.index, 0);
3467        assert_eq!(param.ident, "token");
3468    }
3469
3470    #[test]
3471    fn find_simd_token_param_with_where_clause() {
3472        let f: ItemFn = syn::parse_str(
3473            "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default {}",
3474        )
3475        .unwrap();
3476        let param = find_simd_token_param(&f.sig).unwrap();
3477        assert_eq!(param.index, 0);
3478    }
3479
3480    #[test]
3481    fn find_simd_token_param_with_lifetime() {
3482        let f: ItemFn =
3483            syn::parse_str("fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a f32 {}")
3484                .unwrap();
3485        let param = find_simd_token_param(&f.sig).unwrap();
3486        assert_eq!(param.index, 0);
3487    }
3488
3489    // =========================================================================
3490    // autoversion — tier resolution
3491    // =========================================================================
3492
3493    #[test]
3494    fn autoversion_default_tiers_all_resolve() {
3495        let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3496        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3497        assert!(!tiers.is_empty());
3498        // scalar should be present
3499        assert!(tiers.iter().any(|t| t.name == "scalar"));
3500    }
3501
3502    #[test]
3503    fn autoversion_scalar_always_appended() {
3504        let names = vec!["v3".to_string(), "neon".to_string()];
3505        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3506        assert!(
3507            tiers.iter().any(|t| t.name == "scalar"),
3508            "scalar must be auto-appended"
3509        );
3510    }
3511
3512    #[test]
3513    fn autoversion_scalar_not_duplicated() {
3514        let names = vec!["v3".to_string(), "scalar".to_string()];
3515        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3516        let scalar_count = tiers.iter().filter(|t| t.name == "scalar").count();
3517        assert_eq!(scalar_count, 1, "scalar must not be duplicated");
3518    }
3519
3520    #[test]
3521    fn autoversion_tiers_sorted_by_priority() {
3522        let names = vec!["neon".to_string(), "v4".to_string(), "v3".to_string()];
3523        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3524        // v4 (priority 40) > v3 (30) > neon (20) > scalar (0)
3525        let priorities: Vec<u32> = tiers.iter().map(|t| t.priority).collect();
3526        for window in priorities.windows(2) {
3527            assert!(
3528                window[0] >= window[1],
3529                "Tiers not sorted by priority: {:?}",
3530                priorities
3531            );
3532        }
3533    }
3534
3535    #[test]
3536    fn autoversion_unknown_tier_errors() {
3537        let names = vec!["v3".to_string(), "avx9000".to_string()];
3538        let result = resolve_tiers(&names, proc_macro2::Span::call_site());
3539        match result {
3540            Ok(_) => panic!("Expected error for unknown tier 'avx9000'"),
3541            Err(e) => {
3542                let err_msg = e.to_string();
3543                assert!(
3544                    err_msg.contains("avx9000"),
3545                    "Error should mention unknown tier: {}",
3546                    err_msg
3547                );
3548            }
3549        }
3550    }
3551
3552    #[test]
3553    fn autoversion_all_known_tiers_resolve() {
3554        // Every tier in ALL_TIERS should be findable
3555        for tier in ALL_TIERS {
3556            assert!(
3557                find_tier(tier.name).is_some(),
3558                "Tier '{}' should be findable by name",
3559                tier.name
3560            );
3561        }
3562    }
3563
3564    #[test]
3565    fn autoversion_default_tier_list_is_sensible() {
3566        // Defaults should cover x86, ARM, WASM, and scalar
3567        let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3568        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3569
3570        let has_x86 = tiers.iter().any(|t| t.target_arch == Some("x86_64"));
3571        let has_arm = tiers.iter().any(|t| t.target_arch == Some("aarch64"));
3572        let has_wasm = tiers.iter().any(|t| t.target_arch == Some("wasm32"));
3573        let has_scalar = tiers.iter().any(|t| t.name == "scalar");
3574
3575        assert!(has_x86, "Default tiers should include an x86_64 tier");
3576        assert!(has_arm, "Default tiers should include an aarch64 tier");
3577        assert!(has_wasm, "Default tiers should include a wasm32 tier");
3578        assert!(has_scalar, "Default tiers should include scalar");
3579    }
3580
3581    // =========================================================================
3582    // autoversion — variant replacement (AST manipulation)
3583    // =========================================================================
3584
3585    /// Mirrors what `autoversion_impl` does for a single variant: parse an
3586    /// ItemFn (for test convenience), rename it, swap the SimdToken param
3587    /// type, optionally inject the `_self` preamble for scalar+self.
3588    fn do_variant_replacement(func: &str, tier_name: &str, has_self: bool) -> ItemFn {
3589        let mut f: ItemFn = syn::parse_str(func).unwrap();
3590        let fn_name = f.sig.ident.to_string();
3591
3592        let tier = find_tier(tier_name).unwrap();
3593
3594        // Rename
3595        f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
3596
3597        // Find and replace SimdToken param type
3598        let token_idx = find_simd_token_param(&f.sig)
3599            .unwrap_or_else(|| panic!("No SimdToken param in: {}", func))
3600            .index;
3601        let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
3602        if let FnArg::Typed(pt) = &mut f.sig.inputs[token_idx] {
3603            *pt.ty = concrete_type;
3604        }
3605
3606        // Scalar + self: inject preamble
3607        if tier_name == "scalar" && has_self {
3608            let preamble: syn::Stmt = syn::parse_quote!(let _self = self;);
3609            f.block.stmts.insert(0, preamble);
3610        }
3611
3612        f
3613    }
3614
3615    #[test]
3616    fn variant_replacement_v3_renames_function() {
3617        let f = do_variant_replacement(
3618            "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3619            "v3",
3620            false,
3621        );
3622        assert_eq!(f.sig.ident, "process_v3");
3623    }
3624
3625    #[test]
3626    fn variant_replacement_v3_replaces_token_type() {
3627        let f = do_variant_replacement(
3628            "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3629            "v3",
3630            false,
3631        );
3632        let first_param_ty = match &f.sig.inputs[0] {
3633            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3634            _ => panic!("Expected typed param"),
3635        };
3636        assert!(
3637            first_param_ty.contains("X64V3Token"),
3638            "Expected X64V3Token, got: {}",
3639            first_param_ty
3640        );
3641    }
3642
3643    #[test]
3644    fn variant_replacement_neon_produces_valid_fn() {
3645        let f = do_variant_replacement(
3646            "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3647            "neon",
3648            false,
3649        );
3650        assert_eq!(f.sig.ident, "compute_neon");
3651        let first_param_ty = match &f.sig.inputs[0] {
3652            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3653            _ => panic!("Expected typed param"),
3654        };
3655        assert!(
3656            first_param_ty.contains("NeonToken"),
3657            "Expected NeonToken, got: {}",
3658            first_param_ty
3659        );
3660    }
3661
3662    #[test]
3663    fn variant_replacement_wasm128_produces_valid_fn() {
3664        let f = do_variant_replacement(
3665            "fn compute(_t: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3666            "wasm128",
3667            false,
3668        );
3669        assert_eq!(f.sig.ident, "compute_wasm128");
3670    }
3671
3672    #[test]
3673    fn variant_replacement_scalar_produces_valid_fn() {
3674        let f = do_variant_replacement(
3675            "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3676            "scalar",
3677            false,
3678        );
3679        assert_eq!(f.sig.ident, "compute_scalar");
3680        let first_param_ty = match &f.sig.inputs[0] {
3681            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3682            _ => panic!("Expected typed param"),
3683        };
3684        assert!(
3685            first_param_ty.contains("ScalarToken"),
3686            "Expected ScalarToken, got: {}",
3687            first_param_ty
3688        );
3689    }
3690
3691    #[test]
3692    fn variant_replacement_v4_produces_valid_fn() {
3693        let f = do_variant_replacement(
3694            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3695            "v4",
3696            false,
3697        );
3698        assert_eq!(f.sig.ident, "transform_v4");
3699        let first_param_ty = match &f.sig.inputs[0] {
3700            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3701            _ => panic!("Expected typed param"),
3702        };
3703        assert!(
3704            first_param_ty.contains("X64V4Token"),
3705            "Expected X64V4Token, got: {}",
3706            first_param_ty
3707        );
3708    }
3709
3710    #[test]
3711    fn variant_replacement_v4x_produces_valid_fn() {
3712        let f = do_variant_replacement(
3713            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3714            "v4x",
3715            false,
3716        );
3717        assert_eq!(f.sig.ident, "transform_v4x");
3718    }
3719
3720    #[test]
3721    fn variant_replacement_arm_v2_produces_valid_fn() {
3722        let f = do_variant_replacement(
3723            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3724            "arm_v2",
3725            false,
3726        );
3727        assert_eq!(f.sig.ident, "transform_arm_v2");
3728    }
3729
3730    #[test]
3731    fn variant_replacement_preserves_generics() {
3732        let f = do_variant_replacement(
3733            "fn process<T: Copy + Default>(token: SimdToken, data: &[T]) -> T { T::default() }",
3734            "v3",
3735            false,
3736        );
3737        assert_eq!(f.sig.ident, "process_v3");
3738        // Generic params should still be present
3739        assert!(
3740            !f.sig.generics.params.is_empty(),
3741            "Generics should be preserved"
3742        );
3743    }
3744
3745    #[test]
3746    fn variant_replacement_preserves_where_clause() {
3747        let f = do_variant_replacement(
3748            "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default { T::default() }",
3749            "v3",
3750            false,
3751        );
3752        assert!(
3753            f.sig.generics.where_clause.is_some(),
3754            "Where clause should be preserved"
3755        );
3756    }
3757
3758    #[test]
3759    fn variant_replacement_preserves_return_type() {
3760        let f = do_variant_replacement(
3761            "fn process(token: SimdToken, data: &[f32]) -> Vec<f32> { vec![] }",
3762            "neon",
3763            false,
3764        );
3765        let ret = f.sig.output.to_token_stream().to_string();
3766        assert!(
3767            ret.contains("Vec"),
3768            "Return type should be preserved, got: {}",
3769            ret
3770        );
3771    }
3772
3773    #[test]
3774    fn variant_replacement_preserves_multiple_params() {
3775        let f = do_variant_replacement(
3776            "fn process(token: SimdToken, a: &[f32], b: &[f32], scale: f32) -> f32 { 0.0 }",
3777            "v3",
3778            false,
3779        );
3780        // SimdToken → X64V3Token, plus the 3 other params
3781        assert_eq!(f.sig.inputs.len(), 4);
3782    }
3783
3784    #[test]
3785    fn variant_replacement_preserves_no_return_type() {
3786        let f = do_variant_replacement(
3787            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3788            "v3",
3789            false,
3790        );
3791        assert!(
3792            matches!(f.sig.output, ReturnType::Default),
3793            "No return type should remain as Default"
3794        );
3795    }
3796
3797    #[test]
3798    fn variant_replacement_preserves_lifetime_params() {
3799        let f = do_variant_replacement(
3800            "fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a [f32] { data }",
3801            "v3",
3802            false,
3803        );
3804        assert!(!f.sig.generics.params.is_empty());
3805    }
3806
3807    #[test]
3808    fn variant_replacement_scalar_self_injects_preamble() {
3809        let f = do_variant_replacement(
3810            "fn method(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3811            "scalar",
3812            true, // has_self
3813        );
3814        assert_eq!(f.sig.ident, "method_scalar");
3815
3816        // First statement should be `let _self = self;`
3817        let body_str = f.block.to_token_stream().to_string();
3818        assert!(
3819            body_str.contains("let _self = self"),
3820            "Scalar+self variant should have _self preamble, got: {}",
3821            body_str
3822        );
3823    }
3824
3825    #[test]
3826    fn variant_replacement_all_default_tiers_produce_valid_fns() {
3827        let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3828        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3829
3830        for tier in &tiers {
3831            let f = do_variant_replacement(
3832                "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3833                tier.name,
3834                false,
3835            );
3836            let expected_name = format!("process_{}", tier.suffix);
3837            assert_eq!(
3838                f.sig.ident.to_string(),
3839                expected_name,
3840                "Tier '{}' should produce function '{}'",
3841                tier.name,
3842                expected_name
3843            );
3844        }
3845    }
3846
3847    #[test]
3848    fn variant_replacement_all_known_tiers_produce_valid_fns() {
3849        for tier in ALL_TIERS {
3850            let f = do_variant_replacement(
3851                "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3852                tier.name,
3853                false,
3854            );
3855            let expected_name = format!("compute_{}", tier.suffix);
3856            assert_eq!(
3857                f.sig.ident.to_string(),
3858                expected_name,
3859                "Tier '{}' should produce function '{}'",
3860                tier.name,
3861                expected_name
3862            );
3863        }
3864    }
3865
3866    #[test]
3867    fn variant_replacement_no_simdtoken_remains() {
3868        for tier in ALL_TIERS {
3869            let f = do_variant_replacement(
3870                "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3871                tier.name,
3872                false,
3873            );
3874            let full_str = f.to_token_stream().to_string();
3875            assert!(
3876                !full_str.contains("SimdToken"),
3877                "Tier '{}' variant still contains 'SimdToken': {}",
3878                tier.name,
3879                full_str
3880            );
3881        }
3882    }
3883
3884    // =========================================================================
3885    // autoversion — cfg guard and tier descriptor properties
3886    // =========================================================================
3887
3888    #[test]
3889    fn tier_v3_targets_x86_64() {
3890        let tier = find_tier("v3").unwrap();
3891        assert_eq!(tier.target_arch, Some("x86_64"));
3892        assert_eq!(tier.cargo_feature, None);
3893    }
3894
3895    #[test]
3896    fn tier_v4_requires_avx512_feature() {
3897        let tier = find_tier("v4").unwrap();
3898        assert_eq!(tier.target_arch, Some("x86_64"));
3899        assert_eq!(tier.cargo_feature, Some("avx512"));
3900    }
3901
3902    #[test]
3903    fn tier_v4x_requires_avx512_feature() {
3904        let tier = find_tier("v4x").unwrap();
3905        assert_eq!(tier.cargo_feature, Some("avx512"));
3906    }
3907
3908    #[test]
3909    fn tier_neon_targets_aarch64() {
3910        let tier = find_tier("neon").unwrap();
3911        assert_eq!(tier.target_arch, Some("aarch64"));
3912        assert_eq!(tier.cargo_feature, None);
3913    }
3914
3915    #[test]
3916    fn tier_wasm128_targets_wasm32() {
3917        let tier = find_tier("wasm128").unwrap();
3918        assert_eq!(tier.target_arch, Some("wasm32"));
3919        assert_eq!(tier.cargo_feature, None);
3920    }
3921
3922    #[test]
3923    fn tier_scalar_has_no_guards() {
3924        let tier = find_tier("scalar").unwrap();
3925        assert_eq!(tier.target_arch, None);
3926        assert_eq!(tier.cargo_feature, None);
3927        assert_eq!(tier.priority, 0);
3928    }
3929
3930    #[test]
3931    fn tier_priorities_are_consistent() {
3932        // Higher-capability tiers within the same arch should have higher priority
3933        let v2 = find_tier("v2").unwrap();
3934        let v3 = find_tier("v3").unwrap();
3935        let v4 = find_tier("v4").unwrap();
3936        assert!(v4.priority > v3.priority);
3937        assert!(v3.priority > v2.priority);
3938
3939        let neon = find_tier("neon").unwrap();
3940        let arm_v2 = find_tier("arm_v2").unwrap();
3941        let arm_v3 = find_tier("arm_v3").unwrap();
3942        assert!(arm_v3.priority > arm_v2.priority);
3943        assert!(arm_v2.priority > neon.priority);
3944
3945        // scalar is lowest
3946        let scalar = find_tier("scalar").unwrap();
3947        assert!(neon.priority > scalar.priority);
3948        assert!(v2.priority > scalar.priority);
3949    }
3950
3951    // =========================================================================
3952    // autoversion — dispatcher structure
3953    // =========================================================================
3954
3955    #[test]
3956    fn dispatcher_param_removal_free_fn() {
3957        // Simulate what autoversion_impl does: remove the SimdToken param
3958        let f: ItemFn =
3959            syn::parse_str("fn process(token: SimdToken, data: &[f32], scale: f32) -> f32 { 0.0 }")
3960                .unwrap();
3961
3962        let token_param = find_simd_token_param(&f.sig).unwrap();
3963        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3964        dispatcher_inputs.remove(token_param.index);
3965
3966        // Should have 2 params remaining: data, scale
3967        assert_eq!(dispatcher_inputs.len(), 2);
3968
3969        // Neither should be SimdToken
3970        for arg in &dispatcher_inputs {
3971            if let FnArg::Typed(pt) = arg {
3972                let ty_str = pt.ty.to_token_stream().to_string();
3973                assert!(
3974                    !ty_str.contains("SimdToken"),
3975                    "SimdToken should be removed from dispatcher, found: {}",
3976                    ty_str
3977                );
3978            }
3979        }
3980    }
3981
3982    #[test]
3983    fn dispatcher_param_removal_token_only() {
3984        let f: ItemFn = syn::parse_str("fn process(token: SimdToken) -> f32 { 0.0 }").unwrap();
3985
3986        let token_param = find_simd_token_param(&f.sig).unwrap();
3987        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3988        dispatcher_inputs.remove(token_param.index);
3989
3990        // No params left — dispatcher takes no arguments
3991        assert_eq!(dispatcher_inputs.len(), 0);
3992    }
3993
3994    #[test]
3995    fn dispatcher_param_removal_token_last() {
3996        let f: ItemFn =
3997            syn::parse_str("fn process(data: &[f32], scale: f32, token: SimdToken) -> f32 { 0.0 }")
3998                .unwrap();
3999
4000        let token_param = find_simd_token_param(&f.sig).unwrap();
4001        assert_eq!(token_param.index, 2);
4002
4003        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4004        dispatcher_inputs.remove(token_param.index);
4005
4006        assert_eq!(dispatcher_inputs.len(), 2);
4007    }
4008
4009    #[test]
4010    fn dispatcher_dispatch_args_extraction() {
4011        // Test that we correctly extract idents for the dispatch call
4012        let f: ItemFn =
4013            syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 { 0.0 }").unwrap();
4014
4015        let dispatch_args: Vec<String> = f
4016            .sig
4017            .inputs
4018            .iter()
4019            .filter_map(|arg| {
4020                if let FnArg::Typed(PatType { pat, .. }) = arg {
4021                    if let syn::Pat::Ident(pi) = pat.as_ref() {
4022                        return Some(pi.ident.to_string());
4023                    }
4024                }
4025                None
4026            })
4027            .collect();
4028
4029        assert_eq!(dispatch_args, vec!["data", "scale"]);
4030    }
4031
4032    #[test]
4033    fn dispatcher_wildcard_params_get_renamed() {
4034        let f: ItemFn = syn::parse_str("fn process(_: &[f32], _: f32) -> f32 { 0.0 }").unwrap();
4035
4036        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4037
4038        let mut wild_counter = 0u32;
4039        for arg in &mut dispatcher_inputs {
4040            if let FnArg::Typed(pat_type) = arg {
4041                if matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
4042                    let ident = format_ident!("__autoversion_wild_{}", wild_counter);
4043                    wild_counter += 1;
4044                    *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
4045                        attrs: vec![],
4046                        by_ref: None,
4047                        mutability: None,
4048                        ident,
4049                        subpat: None,
4050                    });
4051                }
4052            }
4053        }
4054
4055        // Both wildcards should be renamed
4056        assert_eq!(wild_counter, 2);
4057
4058        let names: Vec<String> = dispatcher_inputs
4059            .iter()
4060            .filter_map(|arg| {
4061                if let FnArg::Typed(PatType { pat, .. }) = arg {
4062                    if let syn::Pat::Ident(pi) = pat.as_ref() {
4063                        return Some(pi.ident.to_string());
4064                    }
4065                }
4066                None
4067            })
4068            .collect();
4069
4070        assert_eq!(names, vec!["__autoversion_wild_0", "__autoversion_wild_1"]);
4071    }
4072
4073    // =========================================================================
4074    // autoversion — suffix_path (reused in dispatch)
4075    // =========================================================================
4076
4077    #[test]
4078    fn suffix_path_simple() {
4079        let path: syn::Path = syn::parse_str("process").unwrap();
4080        let suffixed = suffix_path(&path, "v3");
4081        assert_eq!(suffixed.to_token_stream().to_string(), "process_v3");
4082    }
4083
4084    #[test]
4085    fn suffix_path_qualified() {
4086        let path: syn::Path = syn::parse_str("module::process").unwrap();
4087        let suffixed = suffix_path(&path, "neon");
4088        let s = suffixed.to_token_stream().to_string();
4089        assert!(
4090            s.contains("process_neon"),
4091            "Expected process_neon, got: {}",
4092            s
4093        );
4094    }
4095}